PrivateautoPrivate ReadonlybyEvery loader, bucketed by geometry kind, path → loader.
One bucket per LOADER_TYPES entry, created up front so
loadersOf never has to handle a missing bucket. The kind-specific
accessors below are thin views onto these same Map objects — callers that
hold registry.loaders and mutate it directly are mutating this store, as
they always were.
LOADER_TYPES, not GEOMETRY_TYPES: a bucket here presupposes a loader
interface in LoaderByKind, so this must be the viewer-drawable
subset. Building it from the wider leaf vocabulary would allocate a bucket
for a type loadersOf can never be called with.
ReadonlyfailedError tracking for failed loaders
Points loaders indexed by scene path
Lines loaders indexed by scene path
GSplats loaders indexed by scene path
Mesh loaders indexed by scene path
Total number of loaders across all geometry types.
Whether there are any registered loaders.
Loaders of one geometry kind, keyed by scene path, narrowed to that kind's loader interface via LoaderByKind.
The single cast in this class: the heterogeneous store cannot express
"bucket K holds LoaderByKind[K]" internally, so the invariant is
enforced at this boundary and every caller above it is fully typed.
Register a loader for a given path. The loader type must match the kind —
register('points', p, someLinesLoader) is a compile error.
Drop a single loader so it no longer participates in scene-wide
updateView sweeps. Defensive: lazy substitutive LOD levels are never
registered in the first place (they stay out of the sweep by design — see
load-lod-group-node.ts; the registry drives their reloads), so on the
lazy-release path this is a no-op. It exists so a future path that DOES
register such a loader cannot leak it into the sweep after its geometry was
released. The loader object itself stays alive in the lod_group's
ensureLoaded closure for reload.
Register a lines loader for a given path.
Register a gsplats loader for a given path.
Register a mesh loader for a given path.
Find which geometry kind owns a given path, or null if none does.
Nothing prevents the same path from being registered under two kinds, so
this has a documented precedence: points > lines > gsplats. That order is
now the bucket insertion order, which comes from LOADER_TYPES —
i.e. from the order of loader_types in format-contract/contract.yaml.
Reordering that list would silently reorder this precedence; the
"checks points first when a path collides" test is the guard.
Record a loader failure. kind defaults to the heuristic classification of
error; pass it explicitly when the caller already computed one.
The single writer for failedLoaders — the retry and update-sweep paths
route through here rather than calling .set inline, which also removes a
pre-existing skew where those two baselined retryCount at 1 and 0.
Bumps the diagnostic retryCount but leaves autoRetryCount untouched —
only markAutoRetryAttempt charges the automatic-retry budget.
Optionalkind: LoaderErrorKindCharge one automatic (connectivity-triggered) retry attempt against a
path. No-op if the path has no failure record. Kept separate from
recordFailure so update sweeps and manual retries — which also record
failures — cannot drain the automatic budget gated by
autoRetryablePaths.
Clear failure tracking for a specific path.
Get information about failed loaders (read-only view).
Whether there are any failed loaders.
Paths an AUTOMATIC retry should attempt: a transient (Network) cause that
is still under MAX_AUTO_RETRY_ATTEMPTS.
A manual Retry deliberately ignores both filters — the user pressing the button is new information (they may have just fixed the server), and a deterministic failure is still worth one more look on request.
Whether any failed loader is worth an automatic retry.
Clear all failure tracking.
Dispose all loaders and clear all maps.
Registry that manages all geometry loaders (Points, Lines, GSplats, Mesh) and tracks loading failures for retry/recovery.