Optionalid: stringOptionalprofiler: UpdateProfilerOptionalmonitorFactory: SceneLoaderMonitorFactory | nullOptionallodGroupRegistryFactory: SceneLoaderLODGroupRegistryFactory | nullOptionaldecodeKTX2: KTX2TextureDecoder | nullPrivate_PrivatecachingPrivatel0PrivateslicePrivatecachePrivateregistryPrivate ReadonlylinePrivate ReadonlyrefinementScene-wide byte-ceiling record for progressive refinement, surfaced on the debug snapshot (#2508); lifetime contract on RefinementResidencyStop.
DELIBERATELY NOT CLEARED BY dispose(), because production reaches a new
scene only through SceneLoaderManager.createLoaderAsync, which builds a
fresh loader and reporter — a reset here would be code no path executes.
Only the in-place dispose() in scene-loader/lifecycle/load-scene.ts
would notice: it nulls _gpuBufferPool for good (the pool is constructed
only in this class's constructor, so SceneLoaderManager.gpuPoolStats()
then returns undefined and gpuPool is ABSENT from every later snapshot)
while this readonly reporter survives. Latent regardless — reusing one
SceneLoader across two loadScene() calls is unsupported (class docstring).
PrivaterefinementPrivatelastThe last refinement run's residency budget, kept so the data monitor can
tell a rung held at the residency ceiling from one still streaming
(refinementHoldReason). Replaced at every run start.
PrivateviewPrivateconfigPrivaterootPrivatemonitorOptional monitor port — populated at construction by the factory
passed in via SceneLoaderManager.setMonitorFactory. Null when no
UI is wired up (tests, embedders), in which case all monitor calls
become no-ops at the call sites.
PrivatearrayPrivate_ReadonlynodePrivateprofilerPrivate_Private_Private_PrivatearchivePrivate_True for the duration of a progressive-LOD refinement run
(scheduleGSplatsRefinement).
Refinement does not take the serialization lock — it INHERITS it: the
update tail (update-view/queue-next.ts) and the post-load kick
(lifecycle/load-scene.ts) hand _updateInProgress = true straight to the
orchestrator, whose final (mesh) phase releases it. So the lock stays
latched for the whole additive-ladder drain, which happens strictly AFTER
the current view has already been committed to the GPU.
This flag marks that stretch so consumers meaning "is data still arriving for the current view" can subtract it — see isLoadPassInProgress. Consumers that mean "is the loader busy at all" keep reading isUpdateInProgress.
Private_Private_Monotonic view-generation counter, read by the LOD registry as
currentViewVersion and stamped onto every committed leaf as
userData.loadedViewVersion (freshness is EXACT equality — see
scene/lod-freshness.ts).
CONTRACT: it bumps ONLY when a query-determinant of the view changes
(viewStatesEqual: displayDims / slicePosition / tolerance / the
dimensions query signature). A pass whose merged view state equals the
current one — a partition resync, a retry re-run, a depth-sort re-commit —
re-sweeps under the SAME version. This matters because lazy lod_group
levels are NOT in the sweep and are never re-stamped by it: bumping on an
unchanged view invalidated every resident fine level scene-wide, so a
partition part crossing the screen edge dropped ALL LOD groups to coarse
and re-streamed them (the #2366 regression on the h2afva 44-part scene).
Private_Targeted-resync paths that arrived while a pass was in flight. Folded into the pass that runs next (or dropped when a full pending state supersedes them — a full sweep is a superset). Never aborts the in-flight pass.
Private_Resync paths handed to the follow-up pass by queueNext's re-entry.
Private_True while the pending slot holds the empty state a RESYNC queued (so a
later rising edge may merge into _queuedResyncPaths); false once any
other caller sets the slot — a real view change or an untargeted
reprocess — because that pass must sweep everything and a stash arriving
on top of it must be dropped, not merged (it would narrow that pass).
Private_Passes actually run (every updateView that reached the sweep). The
per-geometry processors gate their first-update info logs on this being
<= 1; it used to be _updateVersion, which now only advances on a
view CHANGE and would keep those logs on forever for a static scene.
Private_Private_Private_PrivateviewView-state queue: owns _pendingViewState (set/take/has + drain)
and the per-loader previous view-state map used by predictive
prefetch. See ./scene-loader/view-state/view-state-queue.ts.
The pending-state slot is overwritten on every queued update, so a burst of view changes during an in-flight retry collapses to a single drained call (latest-wins). The per-node prev-state map is reset on dataset switch (loadScene) and dispose; skipped paths forget their snapshot so the next non-skip update re-baselines.
Private_Per-dataset AbortController. Created on every loadScene and
aborted at the START of the next loadScene (and on dispose)
so worker tasks queued by the previous dataset settle
immediately instead of running to completion against a
superseded scene. The signal is registered with the WorkerPool
via setAbortSignal. WASM execution itself cannot be cancelled,
but the orphan results are discarded — see WorkerAbortError.
Private_Per-update AbortController. Created at the start of each in-flight
updateView and aborted in the supersede branch when a newer view-state
arrives (and on dispose). Its signal is threaded into the per-type
handler ctxs → loader.updateView → the L0 proxy chokepoint, so a
superseded update's chunk reads/decodes bail with an AbortError instead
of running to completion, and its geometry commit is skipped (the winning
update commits the correct frame). DISTINCT from
_datasetAbortController: it is per-update, NOT registered via
WorkerPool.setAbortSignal (which replaces, not chains); it composes
with the dataset signal through the worker pool's combineSignals.
Private_Waiters for "the requested-or-newer view-state completed a main pass".
Created by the queued/supersede branch, a same-view join, or a superseded
direct pass awaiting its replacement. Each waiter carries the generation
it needs; a committing pass of that generation or newer settles it. If no
pass remains pending, queueNext drains waiters covered by the last pass.
This keeps sceneDimsManager.waitForUpdate() and the dimension-animation
pacing gate tied to a completed view. dispose() and archive faults flush
waiters (resolve-only, never reject) so callers cannot hang.
Private_Request generations (#2943). Every view request that starts or queues a
pass takes the next number; a queued state carries its number (keyed on
the state object, which travels unchanged through takePending →
reenterPending) to the pass that eventually runs it. A waiter records
the generation it asked for and is settled only by a pass of that
generation or a newer one — a newer pass merges onto everything an older
request set, so it covers the older waiter.
Private_Private_Generation of the main pass now running (0 while none is).
Private_Newest generation whose main pass has finished.
Private_Background t+1 slice prefetcher (dimension playback). Lazily created on
the first prefetchSlice call; aborted at the top of every updateView
(foreground always preempts); disposed with the loader. Its SHADOW
loader instances share nothing mutable with the foreground loaders —
the S-cache is the only handoff (see slice-prefetcher.ts).
ReadonlylodPer-loader LOD-group registry. Constructed via the factory passed
by SceneLoaderManager so the registry's camera / viewport /
displayDims getters can close over the live SceneManager — which
the data/ layer must not import directly. null when the host
(e.g. headless tests) doesn't supply a factory; in that case the
scene loader still loads lod_group nodes but the per-frame
selector is a no-op (default level renders).
Private_Optional render-loop wake-up, wired by SceneLoaderManager from the
app pipeline (→ AnimationController.startAnimation). Fired after
every geometry commit so late commits — progressive-refinement
passes, failed-load retries, the online auto-retry, lazy LOD loads —
repaint even when the rAF loop has idle-paused meanwhile. The
callback is idempotent on the receiving side (startAnimation
early-outs while animating and re-arms the idle timer), so per-node
calls inside an atomic sweep are harmless. Null in bare/test
loaders → no-op.
Private ReadonlydecodePublic accessor for the zarr store (needed by LabelLoader).
PrivateloadersPrivatelinesPrivategsplatPrivatemeshPrivatefailedPublic accessor for the scene graph built during loadScene().
Latched archive fault for this loader, or null while updates remain usable.
True if the L1/L2 caching store is configured for this loader.
The GPU buffer pool, or null when pooling is disabled. Exposed so the
LOD-group registry's
resident-byte query (getResidentBytes) can read the single VRAM
truth; callers treat a disabled pool as 0 resident bytes, so it is never
over budget and never evicts.
This scene's progressive-refinement BYTE-ceiling stop, or undefined when
refinement never declined a rung.
The reporter is scene-scoped (one per loader) while the residency budget is
rebuilt per refinement run, so this accumulates across runs — which is what
makes it answerable at capture time, long after the run that stopped. Read
by the debug snapshot through SceneLoaderManager.refinementResidencyStop().
Monotonic view-generation version (bumped by updateView only when the
merged view state differs from the current one — see _updateVersion).
The LOD registry reads this to decide whether a level's committed geometry
is fresh for the CURRENT view — a mesh stamped with an older version (its
data still reflects a previous slice/displayDims) is treated as stale so the
registry can show a coarser fresh level until the re-slice commits.
Whether an updateView sweep (fetch/decode/upload) is currently in flight. Exposed for consumers that must treat loading-time frame jank as unrepresentative — e.g. the adaptive DPR manager suppresses probe learning while this is true. Covers the serialized update sweep, not late lazy-LOD commits (those surface as content-change notifications instead).
Whether a LOAD PASS is in flight. Three parts:
updateView pass (fetch / decode / upload) up to
its geometry commit, or a failed-loader retry sweep, which takes the
same lock;_refining);Together they answer "has the data for the view the user asked for arrived yet?".
Refinement is excluded because it runs after the current view has already
been committed, so folding it in would turn this into full-ladder latency
instead of first-commit latency. That is the same distinction
update-view/queue-next.ts already draws where it resolves the pass waiters
at refinement ENTRY rather than completion — the pacing gate there needs
first-commit latency too.
The queued slot counts because the refinement exclusion would otherwise
open a hole big enough to drive a test through. The steady state on any
laddered dataset right after a commit is "lock held, _refining true"; an
updateView arriving then takes the supersede branch above, parks its state
with viewStateQueue.setPending and returns without touching either flag.
The requested slice has not begun loading, yet both flags still describe
the refinement that preceded it — so without this clause a poller would
read idle and conclude the new slice had rendered. hasPending() is true
across exactly that window: the slot is filled in the supersede branch and
cleared by takePending() at the moment the next pass starts (queueNext
before it re-enters updateView, the refinement loop's own loop-top
cancellation check before it hands off, or finalReleaseLock's drain), so
the flag cannot latch busy after a pass begins.
Also outside its scope: the initial loadScene (which only touches the
lock at its very end, to hand it to the post-load refinement kick) and
lazy substitutive-LOD / deferred-partition ensureLoaded promotions, which
run outside any updateView cycle and surface as content-change
notifications instead.
isUpdateInProgress keeps its existing, broader meaning ("the
serialization lock is held, refinement included") for its existing
consumers — the adaptive-DPR manager and core/app/init/pipeline.ts, both
of which want to discount loading-time frame jank for as long as the
loader is doing work of any kind.
PrivateresolveSettle the queued-update waiters covered by generation upToGen (all of
them by default — the dispose / archive-fault flushes); newer waiters stay
parked for the pass that carries their state.
PrivateresolveSettle the waiters the newest finished main pass covers.
Fire one background prefetch pass for the PREDICTED next view (t+1
during playback). Fire-and-forget: returns immediately; the shadow pass
is aborted by the next foreground updateView. The partial is merged
onto a COPY of the current view state — never persisted (a prefetch
must not move the real view; see the stuck-display hazard in
slice-prefetcher.ts).
OptionalladderDepth: number | "auto"Release the prefetcher's shadow loaders (frees their accumulators). Called when playback ends; shadows rebuild lazily on the next play.
Subscribe to archive-fault episodes for this loader. An explicit retry clears the latch; if the archive fails again, listeners are notified again. Listener exceptions are logged and do not propagate to the caller.
OptionalreplayCurrent?: booleanReplay the current fault immediately when one is latched.
PrivatenotifyPrivatereportPrivateinvokeSnapshot of all cache levels (L0, S-cache, L1, L2) for debug and embed tooling.
List datasets currently held by the L1/L2 caching store.
Clear the in-memory L0 decompressed-chunk cache. No-op if absent.
Clear the in-memory L1 metadata/chunk cache. No-op if absent.
Clear the persistent L2 OPFS cache. No-op if absent.
Clear ALL cache tiers (L0 + L1 + L2 + the decoded-slice S-cache).
Resolved per-tier cache budgets from the last setupCaches run (source:
heap / explicit / device-class / fixed), or null before the first scene
load / after dispose. Surfaced for the Settings popover's budget readout.
PrivateapplyReturn a node-attrs record with rendering attributes replaced by the effective values composed along the scene-graph ancestry (root → leaf). Hierarchical composition: opacity/gamma/intensity multiply, offset adds, blending_mode uses the nearest ancestor's choice.
If the scene graph is unavailable, falls back to the node's raw attrs.
Any additional attributes
Optionaltransform?: readonly number[]Transformation matrix (16 elements for a column-major 4x4 matrix).
Typed as readonly number[] because zarr metadata is parsed
dynamically; see import('../types/zarr').Matrix4x4 for the
narrowed 16-tuple shape.
Optionalnd_transform?: NdTransformMapPer-dimension transforms for non-displayed dimensions
Optionalopacity?: numberRendering attributes
Optionalabsorption?: numberOptionalgamma?: numberOptionalintensity?: numberOptionaloffset?: numberOptionalblending_mode?: stringOptionaljoin?: stringLines-only join style at degree-2 polyline joints: 'none' | 'miter' (#790).
Optionalmax_radius?: numberOptionaln_points?: numberOptionallayer?: booleanWhether this node is exposed as a layer in the Layers panel
Optionalvisible?: booleanInitial visibility when the scene loads (default: true). Authoring-time only.
Optionalcolor_data_range?: [number, number]Min/max of color data, computed at encoding time
Optionalamplitude_data_range?: [number, number]Min/max of amplitude data (GSplats), computed at encoding time
Optionalscalar_data_range?: [number, number]Min/max of scalar data (Points/Lines), computed at encoding time
Optionalcolormap?: stringColormap name for scalar-to-color mapping (e.g., "viridis", "green", "custom")
Optionalhas_scalars?: booleanWhether this node has scalar values for colormap lookup
Optionalextend_to_all?: string[]Dimensions to extend visibility across (points visible at all values)
Install (or clear) the render-loop wake-up callback.
Install (or clear) the notification used to arm online failure retries.
Wire (or clear) the projected-density provider the refinement rung gate
reads. Same dependency inversion as setRequestRender: the tracker lives
in scene/, which data/ cannot import. Forwarded by
SceneLoaderManager.setRefinementDensityProvider.
Why this node's next rung is held back, if it is: 'density' when the
density gate deferred it at the current framing, 'budget' when the last
run's residency budget declined it, null otherwise (streaming, or nothing
pending). Density first: a density refusal is also folded into the budget's
declined set, and the camera-dependent reason is the actionable one.
Per-frame hook from the density walk: if any rung the gate deferred now fits (the camera moved in), re-kick refinement. Cheap when nothing is deferred (the common case). Returns how many paths resumed.
Load a complete scene from a Zarr store using chunk-based spatial indexing.
Orchestrates the loading of hierarchical scene graphs, managing spatial indices, attribute inheritance, and dimension metadata. Supports both points and lines with automatic fallback for datasets without spatial ordering.
The loading process:
Complete URL to the Zarr store. Can be: - HTTP URL: 'https://example.com/data.zarr' - Local path: '/path/to/data.zarr' - With query params: 'https://example.com/data.zarr?noCache'
Promise resolving to a THREE.Group containing the complete scene graph. The group's userData contains: - sceneDimensions: Dimension metadata if available - bounds: AABB of all points - nodeCount: Total number of leaf nodes
// Load a scene from HTTP URL
const scene = await sceneLoader.loadScene('https://example.com/data.zarr');
threeScene.add(scene);
console.log(`Loaded ${scene.children.length} top-level nodes`);
// Load with error handling
try {
const scene = await sceneLoader.loadScene(url);
if (scene.children.length === 0) {
console.warn('Scene is empty');
}
} catch (error) {
console.error('Failed to load scene:', error);
// Fallback to default visualization
}
// Access scene metadata after loading
const scene = await sceneLoader.loadScene(url);
const dims = scene.userData.sceneDimensions;
if (dims) {
console.log(`${dims.length}D dataset:`, dims.map(d => d.name).join(', '));
}
MultiLevelCachingStore for caching implementation
PrivatemakeBuild the per-call LoadSceneCtx. Never passes this to the helper.
PrivatederiveDerive a per-node view state from this.viewState, folding in
extend_to_all tolerance overrides and the inverse nd_transform
for the node's path.
The single source of truth for query-state derivation: both the
main update path and retryFailedLoader go through this method so
a retry cannot load a different query region than a fresh update
would. The pre-extraction retry skipped both adjustments, which
could produce a "successful" retry rendering incorrect data.
A fully-extended node (extend_to_all covers all non-displayed dims)
is returned as a NORMAL { skip: false } node whose query is made
slice-INVARIANT (extend-to-all tolerance sentinel + extended dims'
slicePosition pinned to 0), so per-sweep re-queries hit the loader's
same-view no-op. No extend_to_all skip is produced.
Scene-graph path of the node, used to compose the
world nd_transform from this node up to the root.
Node attrs (only extend_to_all is read here).
When true, partial extend_to_all coverage triggers
a tolerance override via getOrComputeExtendedTolerance.
Points/GSplats: true. Lines: false because line bounds
already encode their non-displayed spatial extent.
OptionalextendedToleranceCache?: Map<string, number[]>Optional cross-node cache for the partial-extend tolerance array. Main update path passes one cache per cycle; retry passes nothing (fresh).
PrivaterunShared scaffolding for the per-geometry update loops. The Points / Lines / GSplats branches in updateView differ only in the type-specific work (deriveNodeViewState, call loader.updateView, post-process, setMetadata, return staged) — the surrounding try/catch with failedLoaders bookkeeping + retryCount tracking, profiler dispatch, and Promise.all are all identical and live here.
The map of (path → loader) for one geometry type.
Human-readable type for profiler label + error log.
Per-loader work; returns staged commit data or null when there's nothing to commit.
OptionalresyncPaths: ReadonlySet<string>Re-run the current view state without blocking the caller. With paths
(the partition parts that just re-entered the frustum) the sweep is
restricted to loaders at/under them; otherwise every loader re-runs. Either
way the view state is unchanged, so the view version is NOT bumped.
Optionalpaths: readonly string[]Re-open exhausted progressive ladders after connectivity is restored.
PrivatetakeHand the resync paths parked by a mid-pass call to the follow-up pass.
PrivatereenterRe-enter updateView with a drained pending state. The ONLY way a
pending state may be re-entered: every drain site must consume the resync
paths stashed for it, or they leak into a later, unrelated same-view pass
(e.g. a depth-sort requestReprocess() queued behind a pass) and narrow
it to loaders that have nothing to do with it — leaving the nodes that
pass existed to re-commit stamp-less. A changed view ignores the paths
anyway (see updateView), so passing them along is always safe.
PrivatemergePrivatecanWhether a request can JOIN the main pass in flight instead of superseding
it (#2943): it names a slice (a dims-driven request, not a {} reprocess
that exists to force a re-commit), nothing newer is queued, neither the
request nor the running pass carries a playback directive (a budget-free
refine must still re-run a budgeted pass), and it merges to exactly the
view the running pass is loading.
Update all points and lines for a new view state.
Uses serialized execution to prevent race conditions: only one update runs at a time. If a new update arrives while one is in progress, it's queued as "pending" and processed after the current update completes. Only the LATEST pending state is kept (older ones are discarded), ensuring eventual convergence without starvation.
PrivateanyAny registered loader (points / lines / gsplats / mesh) with LODs left to stream.
PrivateprogressiveSnapshot every sweep-registered progressive ladder, including completed ones.
Kick the progressive refinement orchestrator from OUTSIDE an update pass.
Refinement is normally scheduled only at update-view tails
(queue-next.ts) and after the initial scene load (load-scene.ts) —
loaders that register OUTSIDE those moments otherwise sit at their first
additive chunk until the next slice change. The one such registration
path is a deferred lod_group SUBTREE activation (e.g. the overview
recipe's fine kind=partition branch): its part leaves join the sweep
maps mid-session, so the deferred-group ensureLoaded calls this after
loadChildren settles.
Lock discipline: when idle, take the serialization lock and run the same
orchestrator the other kick sites use (each phase releases/hands off the
lock — see scheduleGSplatsRefinement), with the same belt-and-braces
release on an orchestrator-glue rejection. When an update or refinement
already holds the lock, its own tail probe usually covers the new
loaders — but a sequenced refinement run may already be PAST the new
loaders' geometry phase, so instead of assuming, re-check on a short
timer (single pending re-check; drops out as soon as nothing has more
LODs or this loader is disposed). A plain timer, deliberately NOT
scheduleFrame: that helper runs synchronously when rAF is missing,
which would turn this lock-held re-check into unbounded recursion.
"Busy" is the lock OR a live refinement, not the lock alone: the final
refinement phase's finalReleaseLock opens the lock while _refining
is still set (the flag is cleared one level up, in the orchestrator's
finally, once the phase's await unwinds). A microtask already queued at
that instant — a deferred lod_group ensureLoaded continuation is
exactly one — would read the open lock as idle and start a SECOND
refinement run on top of the first. The two runs share one _refining
boolean, so the first run's finally would clear it mid-flight and
isLoadPassInProgress() would report a load pass for the whole remaining
drain.
PrivatescheduleSchedule progressive GSplats LOD refinement.
Thin wrapper around runGSplatsRefinement in
data/gsplats/lod-refinement.ts. The full timing semantics — rAF
yield per pass, cancellation hand-off on pending view-state, and
lock release on normal completion — live in that module.
PrivatenoteStamp the load timeline's refinementComplete milestone when the final
geometry phase ran every ladder to completion — not on a cancellation
hand-off (the next update re-kicks refinement) and not on a dead loader.
PrivateclearClear the committedData identity stamp on a node's mesh. Called when a
lazy LOD level is demoted: its geometry returned to the evictable pool,
so the stamp (a) no longer describes what's on the GPU and (b) would pin
the released node's large CPU arrays in memory. Re-promotion builds a
fresh loader → new data reference → full recommit either way.
PrivateupdateAggregate visible counts from all lines and gsplats meshes and update monitor. This should be called after view updates to report accurate visible counts.
Recompute the monitor's visible-element tally outside the data-load
cycle. Substitutive LOD selection (LODGroupRegistry.evaluatePerFrame)
swaps which level renders on camera moves with no reload, so the
per-frame callback calls this after a LOD switch — otherwise the
monitor's "visible" counts stay pinned to the level that was active at
the last updateView (e.g. the coarsest default level).
PrivateprocessProcess lines data: compute tolerance, project to 3D (async). Returns staged commit data without mutating any mesh geometry.
Implementation lives in scene-loader/process/data-processor-lines.ts; this
method is a thin delegate so the pipeline can be tested in isolation
without instantiating a SceneLoader.
Optionalsession: UpdateSessionPrivatecommitCommit lines geometry to GPU buffers (synchronous).
Called as part of the atomic commit stage — no async operations allowed.
The optional session is forwarded so the helper can record an
"Update Buffers" child entry under the per-node profiler session
(parity with Points and GSplats).
Optionalsession: UpdateSessionPrivateprocessProcess gsplats data: project nD to 3D, pack Cholesky factors (async). Returns staged commit data without mutating any mesh geometry.
Implementation lives in scene-loader/process/data-processor-gsplats.ts.
Optionalsession: UpdateSessionPrivatecommitCommit gsplats geometry to GPU buffers (synchronous).
Called as part of the atomic commit stage — no async operations allowed.
The optional session is forwarded so the helper can record an
"Update Buffers" child entry under the per-node profiler session
(parity with Points and Lines).
Optionalsession: UpdateSessionPrivateprocessProject + stage a mesh for commit.
Implementation lives in scene-loader/process/data-processor-mesh.ts. Async
only because backend selection is, so unlike the lines/gsplats twins this never
resolves to null — there is no worker projection that can decline.
PrivatecommitCommit mesh geometry (synchronous).
Implementation lives in scene-loader/commit/commit-mesh-geometry.ts. Takes no
GPU buffer pool: a mesh's vertex buffers are uploaded once per displayDims
epoch and never resized, so there is nothing for the pool to recycle.
Optionalsession: UpdateSessionPrivatemakeBuild the per-call NodeBuildCtx for the initial-load leaf helpers.
Snapshots viewState + factoryDeps so a concurrent updateView can't
mutate state mid-flight. Never passes this.
PrivatefactoryBuild the per-call dependency snapshot for the loader factory.
PrivateconnectConnect a loader to the data-loading monitor. Implementation lives
in scene-loader/nodes/connect-loader-to-monitor.ts.
PrivateupdateUpdate geometry for a specific points node.
Implementation lives in scene-loader/commit/commit-points-geometry.ts.
Optionalsession: UpdateSessionPrivateprocessStage points data for commit — the Points arm of the shared
process/commit pair. A pass-through: points arrive display-ready from
their loader (see data-processor-points.ts).
PrivatecommitCommit staged points data. Delegates to updatePointsGeometry so the one-shot and two-stage forms cannot diverge.
Optionalsession: UpdateSessionPrivateinitializeInitialize scene dimensions from metadata. Implementation lives in
scene-loader/nodes/initialize-scene-dimensions.ts; null return
means validation failed and the existing viewState stays.
Whether candidate describes the view this loader has ALREADY committed
(same displayed dims, slice, tolerances and per-dim query signature, by
viewStatesEqual). Used by updateSceneForDimensions to turn the
post-load updateAllNDNodes — fired unconditionally after loadScene
has fetched, decoded, projected and committed every node at exactly this
state — into a no-op instead of a second full pass (measured: L0 hits ==
misses on every 3-D scene, and ~1 s of extra main-thread work on a
29.6 M-splat slide). A real slider change compares unequal and proceeds.
False while a requested main pass has not finished: this.viewState
names a pass's view as soon as it starts. This also covers the frame
boundary between a superseded pass and its queued replacement, when no
main pass is running but the old view never committed (#2943).
PrivatenormalizeNormalize URL for zarr store access. Guards window so this works
in non-DOM contexts (tests, embed-in-Worker scenarios). Absolute URLs
ignore the origin entirely; the fallback only matters for relative
paths.
Show the monitor UI
Hide the monitor UI
Toggle the monitor UI
Get information about failed loaders
Map of loader paths to error information
Whether a path or one of its descendants has a recorded network failure.
Build a FailedLoadsProviderPort over the failed-load set for a UI
consumer — the data-monitor's failure banner (wired in monitor-wiring.ts)
and the layers panel's per-row error badge (wired in
core/app/dataset/load-dataset.ts). Each call returns a NEW provider object
(the monitor and the panel hold distinct instances), but all of them read
the SAME live loader failures, archive-fault latch, and latched lazy
branches through the one retryAllFailedLoaders entry point, so they
always agree.
getFailedReason powers the layers-panel tooltip.
PrivategetPrivateclearPrivateresumeCheck if there are any failed loaders
Whether any failure is worth an AUTOMATIC retry: a latched archive fault,
a transient loader cause still under the attempt cap (see
LoaderRegistry.autoRetryablePaths), or a lazy branch latched by an archive
fault. The connectivity-triggered retry gates on this so deterministic
ordinary loader failures remain quiet while reconnecting can re-open the
loader and deferred LOD work.
Clear failed loader tracking Useful for retry operations or after user acknowledges errors
Retry loading a specific failed loader.
Re-triggers the update for a failed loader using the current view state. Useful for recovering from transient network errors or after connectivity is restored.
The path of the failed loader to retry
Promise resolving to true if retry succeeded, false if failed or not found.
For a LAZY substitutive LOD level (not in the sweep maps), true
means the deferred reload was KICKED (fire-and-forget) — the lazy
thunk owns the eventual ready/failed outcome, and a repeat failure
re-records itself for another retry.
Registered lines paths first wait for the session working-set gate,
so this call can remain pending behind an eager scene walk.
PrivatemakeBuild the per-call RetryCtx. Never passes this to the helper.
Retry all failed loaders. Useful for batch recovery after network connectivity is restored.
OptionalonlyAutoRetryable?: booleanRetry only entries that pass the automatic-retry filter (a latched archive fault, or a transient cause under the attempt cap). Set by the connectivity-triggered retry. A manual Retry omits it and forces every failed path.
Promise resolving to { succeeded, failed, deferred? }.
deferred: true means NOTHING was retried — a main update held
the serialization lock, so the batch was refused (every path is
reported in failed for compatibility, but none genuinely
re-failed). Callers must not present a deferred result as a
failed re-attempt; retry again once the update settles.
Dispose of all resources.
Async because the caching store dispose path drains the prefetcher,
cancels in-flight validation, and flushes OPFS metadata — work that
a dataset switch should wait for before constructing the next
loader. Existing sync callers (the beforeunload path,
SceneLoaderManager.destroyLoader/destroyAll) still work; the
returned promise just unwinds in the background. Callers that need
deterministic teardown should await this method or use
SceneLoaderManager.destroyLoaderAsync (added in a follow-up
commit).
Worker pool policy: Web Workers used for projection/decoding live
in a MODULE-LEVEL singleton (workers/worker-pool.ts:getWorkerPool),
not per-SceneLoader. Dataset switches deliberately
do NOT terminate workers — the pool is bounded, and tearing it down
per switch would force a fresh worker spin-up on the next load
(10s of ms of WASM re-init on each cycle). Workers are terminated
only at app shutdown via disposeWorkerPool() in core/app.ts,
which is the right scope for that lifecycle.
Main scene loader that handles the complete loading pipeline.
Features:
Lifecycle: one-shot. Each
loadScene()call disposes prior loaders + caches and nulls the monitor reference. Reusing a singleSceneLoaderinstance across twoloadScene()calls is unsupported and will leave the second load with a null monitor reference. UseSceneLoaderManager.createLoader()(the canonical entry point indata/zarr-loader.ts), which constructs a fresh loader per load — the SceneLoaderManager handles the destroy/recreate dance for you.