Private ReadonlydecoderPrivate ReadonlyrangePrivate ReadonlyzarrPrivate Optional ReadonlydecodePrivatehandlesMetadata-only handles, opened once by initialize.
PrivatepreflightWhat Stage 1 established.
PrivateinitSerializes concurrent calls into initialize onto one metadata open.
initialize() used to guard only with if (this.handles) return before its
awaits — the exact "async initialization race" pattern the repo's own
CLAUDE.md calls out. fetch()/load() were the only caller before
runPreflight made initialize reachable from a second, independent
entry point, so a concurrent runPreflight() + loadMesh() opens every
.zarray/.zattrs twice and runs preflightMesh twice. Mirrors the
inFlight latch on load: cleared once the attempt settles, and only
if it is still OURS — a later call may already have installed a fresh
attempt after this one settled — so a transient failure is retried rather
than cached forever.
PrivatedataThe decoded mesh, cached for the loader's lifetime.
This is the whole point of the whole-node design: one fetch per node, and
every later updateView is served from here.
PrivateinSerializes concurrent first loads onto one fetch (see load).
PrivatelifetimeThe current lifetime (see LoaderLifetime): its signal aborts the
in-flight reads when the loader is torn down, and its IDENTITY is the
generation token that stops a post-dispose completion from repopulating
the cache. Both matter: the token alone would let a near-budget transfer
(up to half a gigabyte) run to completion for nothing, concurrently with
whatever replaced the node; the abort alone would still let a settle that
raced it publish. Replaced (not latched) on dispose: mesh disposal
is state-clearing, so the reuse-after-dispose path starts with a fresh,
live lifetime.
PrivatefetchThe signal governing the current fetch, sourced by the shared
RangeLoader through its thunk — that loader takes its abort signal
from a callback rather than a parameter, so the colour path picks this up
without any signature change. Single-flight (inFlight) makes one field
sufficient.
NAMED DIFFERENTLY from its three siblings on purpose. Points, Lines and GSplats
each hold a per-call _lifetime.calls context and wire the identical
setSignalSource(() => this._lifetime.calls.signal) one line into their constructors, so
this looks like a symmetry break — it is a lifetime difference. Their signal is
per call, including concurrent updates. Mesh is whole-node resident, so it
fetches ONCE and then serves every later updateView from this.data without
any I/O; this field is live only for that single fetch and is null during the
scrubs that make up almost all of a session. Calling it "active" would claim the
opposite of what holds.
Private ReadonlymetricsMonitor telemetry for this node — see the LoaderMonitor section at the bottom of the class for what a whole-node loader can honestly report.
Private ReadonlyeventsMonitor listeners, shared implementation with the three sibling facades.
Private ReadonlypathPrivate ReadonlyattrsPrivate ReadonlylocationPrivateinitializeOpen metadata handles for every array the attrs declare, then run Stage 1.
Single-flight: a concurrent second call joins the first attempt rather than opening the same metadata again (see initInFlight).
PrivatedoOpen metadata handles for every array the attrs declare, then run Stage 1.
Opening is metadata-only — zarr.open(..., { kind: 'array' }) reads
.zarray and .zattrs and nothing else — which is what makes it safe to do
for all declared arrays, including the string-channel CSR pairs v1 never fetches.
The budget has to see them to be a budget.
Captures the current lifetime up front and guards BOTH publishes below
with it, mirroring load's own lifetime guard. Without this, a dispose()
racing an in-flight doInitialize() — reachable with no load() in
flight at all, since runPreflight calls this too, including during
a dataset switch — would still let the metadata open complete and publish
this.preflight / this.handles onto a torn-down loader, making the
dispose() docstring's "a subsequent loadMesh re-initializes" false in
that window: the stale, non-null handles would look already-initialized.
Run Stage 1 (the metadata-only preflight) eagerly, and return what it established.
Exists for one caller: MeshProgressiveLoader.assertWithinByteBudget
(./mesh-progressive-loader.ts), which needs to charge every level of a
reveal ladder against ONE byte budget before any level's chunk data is
fetched — each level's own preflight only ever sees its own
MESH_DECODE_BUDGET_BYTES ceiling, so nothing else adds them up. This is
just initialize (idempotent — a load() that runs later reuses the
SAME cached handles/preflight, so calling this first duplicates no
request) with its result surfaced instead of stashed on a private field.
Fetches no chunk data, same as the preflight it wraps.
PrivatefetchFetch + decode everything, then run Stage 2.
Ordering is deliberate: faces is validated and widened before it is
stored on LoadedMeshData, so no caller can ever observe an
unvalidated index array.
Optionalsignal: AbortSignalPrivateloadServe the mesh, fetching it at most once per loader instance.
The inFlight latch is not defensive padding. Both entry points below
funnel here, and the scene-loader legitimately calls updateView while an
initial loadMesh is still in flight (a slice scrub during load). Without
the latch each call would start its own full-mesh fetch — the exact
duplicate-work the whole-node design exists to avoid.
Sharing that fetch decides whose signal governs it: the reads run under the
LOADER-LIFETIME signal only (dispose aborts it), never under a
caller's. Each caller instead WAITS on its own signal (abortableWait):
a superseded update is released with an AbortError at once, while the
fetch keeps going for everyone else. Running it under the first caller's
signal would be wrong in exactly the case the latch exists for — during a
scrub over a cold mesh every pass is superseded by the next, so each would
abort the fetch the later passes had joined and the mesh would never land.
Letting a fetch outlive the caller that started it is benign here in a way
it would not be for the range-query siblings: there is only one thing to
fetch and it does not depend on the view, so the next pass wants exactly
these bytes. The wasted work is bounded by one mesh, once per loader.
The lifetime signal reaches EVERY read: the raw faces read directly, the
decoder-routed arrays through ArrayDecoder.decode's signal
parameter, and the shared colour path through the RangeLoader
signal-source thunk wired in the constructor.
Optionalsignal: AbortSignalPrivatestartStart the ONE shared fetch load hands to every caller.
Load the mesh. viewState is accepted for interface symmetry and unused.
Re-serve the mesh for a new view state.
Returns the cached whole mesh — a view change never re-fetches, because
there is no subset to fetch. The visibility cull that does depend on the
view runs downstream in projection.ts.
Optional_session: UpdateSessionOptionalsignal: AbortSignalupdateView, plus whether the mesh was already in hand.
Exists for one caller: MeshProgressiveLoader (./mesh-progressive-loader),
whose streaming loop
asks each level "was that cheap?" to decide whether to keep going this pass
or leave the rest to a later one (streaming-policy.ts). The three sibling
progressive loaders call the identically named method on their spatial-index
sub-loaders, so the ladder loop is the same shape for all four types.
allResident reads the loader's OWN decode cache rather than the chunk
cache the siblings report, because that is where the cost actually is here:
a whole-node level either has been fetched and decoded (free to re-serve) or
has not (a full network read). Sampled BEFORE the await, so a level that this
very call fetches reports false — reporting the post-fetch state would say
"resident" for every level and defeat the refine pass's stop rule.
Optionalsession: UpdateSessionOptionalsignal: AbortSignalRelease the decoded payload after a progressive parent has folded it into its cumulative result. Initialization metadata and handles stay warm, so a later direct load can re-fetch without rebuilding the loader.
preserveExternalResources is used when the parent's cumulative result is
the same single-level object; closing its bitmap would invalidate the
transferred payload.
LoaderMonitor surface (optional, for the data-loading-monitor UI).
Mirrors the surface the sibling loaders expose — implementations that
don't track metrics may omit these. Both shipped implementations
(MeshWholeNodeLoader, MeshProgressiveLoader) provide all four:
connectLoaderToMonitor duck-types the complete set, so a partial
implementation is silently skipped rather than partially reported.
Always empty: a whole-node loader runs no spatial queries, so none can be
in flight. The in-flight FETCH is reported through loads instead.
Record how many of this node's triangles the current slice indexes.
Called by commit-mesh-geometry.ts from the same place it stamps
userData.visibleTriangleCount, because that count is produced DOWNSTREAM
of the loader: projection decides which faces the index buffer receives,
and the loader (which holds the whole mesh either way) cannot know it. The
three sibling loaders set visibleElements themselves for the opposite
reason — for them the query result IS the visible set.
PrivaterecordFold one completed fetch into the metrics and emit the monitor load event
that feeds the panel's load-rate and bandwidth windows.
retained is false when the loader was disposed while the fetch was in
flight. The cumulative counters still take it — those bytes were really
spent — but memoryUsed is a LIVE footprint, and the payload was dropped
rather than published, so claiming it would leave the panel reporting
resident memory for a torn-down node.
PrivaterecordRecord a failed fetch — EXCEPT a deliberate abort.
A dataset switch or a dispose during load aborts the in-flight reads (dispose), which is a control path, not a failure: counting it would raise the advisor's error-rate recommendation every time the user switches scenes. Same exclusion the sibling loaders apply through isAbortError.
Clear all cached state.
State-clearing rather than terminal — unlike the spatial-index siblings,
whose LoaderLifetime latch makes disposal final: a subsequent loadMesh
re-initializes and re-fetches rather than throwing.
Data loader interface for Mesh nodes.
Mirrors
DataLoader/LinesDataLoader/GSplatsDataLoaderso the scene-loader machinery treats all four kinds uniformly — and so a spatial-index implementation can be swapped in behind this interface later with no caller change (spec §7).The
viewStateparameters are honoured but do not change what is fetched: a mesh is whole-node resident, so both methods return the same complete LoadedMeshData and the view state matters only downstream, where the cull decides which faces are indexed.