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).
PrivateaborterAborts the in-flight reads when the loader is torn down.
The generation token below stops a post-dispose completion from repopulating the cache, but on its own it lets the transfer RUN to completion first — for a near-budget mesh that is up to half a gigabyte fetched and decoded for nothing, concurrently with whatever replaced the node. Aborting the reads stops the spend, not just the publish. Replaced (not just aborted) on dispose so the reuse-after-dispose path starts with a live signal.
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 _activeSignal and wire the identical
setSignalSource(() => this._activeSignal) one line into their constructors, so
this looks like a symmetry break — it is a lifetime difference. Their signal is
per-UPDATE: set at the top of every updateView and cleared in its finally,
live whenever the loader is doing anything. 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.
PrivategenerationBumped by dispose, so a fetch that settles afterwards cannot write its result back into a loader that has been torn down.
Without it, dispose() during an in-flight load leaves the completion free to
repopulate this.data — resurrecting the cache on a dead loader, and for a
near-budget mesh pinning up to half a gigabyte that nothing will ever read.
Cheap enough that the whole-mesh payload makes it worth having.
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 generation up front and guards BOTH publishes below with it,
mirroring load's own generation 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.
One consequence of sharing that fetch: the reads run under the FIRST
caller's signal combined with the loader-lifetime one (dispose
aborts the latter). A later caller joining an in-flight fetch cannot abort
it and will receive the mesh even if its own update was superseded. That is
benign here in a way it would not be for the range-query siblings — a
superseded caller gets data it no longer needs, never data for the wrong
query, because there is only one thing to fetch and it does not depend on the
view. The wasted work is bounded by one mesh, once per loader.
The combined 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: AbortSignalLoad 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, matching the sibling loaders (the points
loader's dispose likewise drops its arrays and calls _onceInit.reset()):
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.