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.
Register a lines loader for a given path.
Register a gsplats loader for a given path.
Register a mesh loader for a given path.
Geometry kinds with at least one registered loader that still has
additive rungs to stream (hasMoreLODs === true; non-progressive loaders
have no such property). The one probe the refinement scheduling sites
share.
Whether any registered loader still has additive rungs to stream.
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.