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(): a loader loads one dataset and
dispose() is terminal (a second loadScene() throws), so a new scene
always gets a fresh loader and reporter through
SceneLoaderManager.createLoaderAsync.
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.
PrivateviewPrivate_See committedViewState; null until a load or pass commits.
PrivateconfigPrivaterootPrivatemonitorOptional 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_PrivatearchivePrivate_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_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_Private_Set by the first loadScene; a loader loads exactly one dataset.
Private_Private_path → node + world nD transform index of _sceneGraph, built once
with it (the graph never changes afterwards). Every per-node derivation and
prefetch lookup reads it instead of walking the graph.
Private_PrivateviewView-state queue: the pending slot (whose lifecycle passes drives) 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 cleared on dispose (the loader is one-shot, so there is no dataset switch to reset it for); skipped paths forget their snapshot so the next non-skip update re-baselines.
Private_The dataset's AbortController. Created by the (one-shot) loadScene and
aborted on dispose — which a dataset switch runs on this loader before
creating a fresh one — so worker tasks it queued settle immediately
instead of running to completion against a superseded scene. The loader threads it into its own worker
projections (withDatasetSignal); the pool itself is shared by
every host on the page and carries no dataset signal. WASM execution
itself cannot be cancelled, but the orphan results are discarded — see
WorkerAbortError.
Private ReadonlypassesSerialization of view passes, refinement runs and retries: the lock, the
pending slot's lifecycle, request generations and their waiters, the
resync stash and the B5 drag commit guarantee. See
scene-loader/update-view/pass-scheduler.ts.
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.
drawn is false when the committed node was not drawn either side of
the commit (itself or an ancestor hidden: an LOD level the registry
keeps off screen, a hidden layer), so the frame cannot have changed and
the receiver only keeps the loop ticking. It must still wake it: the LOD
registry polls per frame and may now show the level that just
committed, and that visibility flip requests its own redraw.
Private ReadonlydecodePrivate_The host's depth-sort coordinator, which every commit reports to. Set by
loadScene (data/zarr-loader.ts) before the scene loads, from the
coordinator the caller owns — the LuxarApp's SceneManager.depthSort, or a
LuxarLayer's own. null (tests, a bare loader) leaves the nodes untracked.
Private_The host's material manager, or null for the LuxarApp's. Installed by the
host's SceneLoaderManager; node creation (the factory) and the commit
stage both run against it, so a LuxarLayer's materials never join the
app's registry (capabilities, camera broadcast, disposal).
Public accessor for the zarr store (needed by LabelLoader).
PrivateloadersPrivatelinesPrivategsplatPrivatemeshPrivatefailedPublic accessor for the scene graph built during loadScene().
Path index over sceneGraph (path → node + world nD transform),
built with it in setSceneGraph. Consumers outside the loader that derive
per-node view states (the audio slab rule) read nD transforms from here in
O(1) instead of walking the graph from the root on every derivation.
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.
The view the drawn geometry was last committed for (B4) — set when a load starts and by every committed pass. Before the first commit, the live view.
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;PassScheduler.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/pass-scheduler.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, refinement live"; an
updateView arriving then is parked in the pending slot by
PassScheduler.request 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 by the request and cleared
by takePending() at the moment the next pass starts (the scheduler's
frame yield before it re-enters updateView, or its release 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.
Fire one background prefetch pass for the PREDICTED next view (t+1 during playback). Fire-and-forget: returns immediately. The shadow pass deliberately persists across foreground ticks (a cold level outlives one frame); it is aborted only on playback end / dispose / its stall guard, and a foreground pass for the same slice waits for it rather than re-assembling that slice (see slice-prefetcher.ts). 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"PrivateactivateDeferred partition parts the predicted slice needs (B4) are activated ahead
of it, so its foreground pass finds them registered — and, once they are, a
targeted shadow pass warms them: a shadow can only warm a REGISTERED node,
so the batch prefetchSlice just started cannot reach them. Fire-and-forget:
the activation never rejects.
Release the prefetcher's shadow loaders (frees their accumulators). Called when playback ends; shadows rebuild lazily on the next play.
Connect the owner of per-leaf LIVE appearance state (the Layers panel) to
every data leaf this loader attaches from now on — partition parts the LOD
registry activates, lazily built levels, anything built after load. Each
leaf is reported as its placeholder attaches, before any data reaches it,
together with this loader's scene graph. null disconnects.
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 host's depth-sort coordinator.
Install (or clear) the host's material manager.
PrivatecommitThe host references the gsplats / lines / points commit helpers take.
PrivatecommitRun a commit of the node at path, then wake the render loop (see
_requestRender), saying whether the commit can have changed the
drawn frame: the node was effectively visible before or after it. A node
that cannot be resolved counts as drawn. Returns that verdict.
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>PrivatesweepStage 1 of a pass for every loader pick keeps: load + process each, one
runLoaderUpdates per geometry type, nothing committed.
OptionalresyncPaths: ReadonlySet<string>PrivateregisteredEvery registered loader path, of every geometry type.
PrivatesweepActivate the deferred partition parts this pass's view needs (B4) and, once
their loaders are registered, sweep those loaders as part of THIS pass —
the ones alreadySwept (the main sweep's) does not hold. null when no
part was activated. Must be called synchronously with the main sweep, so
the activation reads the pass's own view. The pass's signal is its claim
on the activations: a superseded pass withdraws it, so an activation that
outlives it resyncs its part rather than waiting on a sweep that never
commits.
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.
PrivatesweepEvery sweep-registered loader of the four geometry kinds, with its path.
PrivatemergePrivatematchesThe view half of a JOIN (#2943): the request names a slice (a dims-driven
request, not a {} reprocess that exists to force a re-commit), neither
it 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. The scheduler adds that a pass is running,
unaborted, with nothing newer queued.
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.
PrivaterunStage 2 of a view pass: the synchronous atomic commit of every staged node
(update-view/atomic-commit.ts). Skipped wholesale when the pass was
superseded (signal) or hit an archive fault (discard); a failing node
commit is rolled back to its pass start and rethrown after its siblings.
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 view-pass tails and after the
initial scene load — 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. Two recovery paths use it too (#2975):
a failed-loader retry that committed only part of a ladder, and the backoff
re-drain of ladders the consecutive-failure cap retired.
When idle it takes the lock and runs the orchestrator; when busy it
re-checks on a timer. "Busy" is the lock OR a live refinement run: the
final phase's release opens the lock while the run is still unwinding, and
a second run started in that window would share the first one's flags.
See PassScheduler.kickRefinementIfIdle.
PrivatescheduleDrain every progressive ladder (points, lines, gsplats and mesh) one rung per frame, under the serialization lock the caller handed over.
The phases run in sequence — gsplats first (its loader was the original
template), then points, lines and mesh — each over the load-eligible
loaders of its type at the moment it starts (kickRefinementIfIdle covers
loaders registered later). On cancellation (a view queued during the run)
the cancelling phase hands the lock to the next pass across a frame and the
later phases are skipped. A failed-loader retry pre-empts the run the same
way (A10): isActive turns false, every phase exits, and the run's end
hands the still-held lock to the retry. Only the LAST phase completing
releases the lock. The per-pass timing — frame yield, cancellation check,
stall exit — lives in progressive/refinement.ts.
PrivatebeginOpen the run's shared residency budget: ONE budget for all four phases, seeded from every sweep-registered ladder. Completed loaders return before their wrapper calls admit(), so seeding is what keeps their resident bytes in later view-triggered runs instead of ratcheting the ceiling upward. Lazy lod_group levels are not in these sweep maps. The density gate is re-evaluated from scratch each run: a deferral is camera-dependent, never sticky across runs.
PrivaterefinementThe four refinement phases in run order, each resolving its type's load-eligible loaders when it STARTS (a snapshot, not the live map).
Mesh runs LAST and therefore releases the lock. Order within the four is otherwise historical (gsplats was the template), but the final slot is not arbitrary: a mesh reveal is the cheapest of the four to interrupt — its levels are already-decoded whole-node payloads, so a cancelled pass loses at most one projection rather than an in-flight chunk fetch.
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.
PrivatescheduleRe-drain ladders the consecutive-failure cap retired (#2975).
A chunk fetch that stalls to give-up fails the refinement step that needs
it, and three such failures retire the ladder for this loader instance.
Only an online event used to re-open it, so a session that stayed online
sat idle at a coarser rung until the user moved something. Instead, after
a drain that ends with such a ladder, wait
ABANDONED_RUNG_RETRY_BASE_MS · 2^round (capped), re-open the retired
ladders and kick a fresh drain. A view change, resetRefinementFailures
or dispose cancels the wait; after MAX_ABANDONED_RUNG_RETRY_ROUNDS
rounds the ladder is left, and the drain reports complete.
Whether a re-drain is pending (the drain is not complete).
PrivateabandonedThe retired ladders still under MAX_ABANDONED_RUNG_RETRY_ROUNDS; one that just ran out is left, with a single warning.
PrivatecancelDrop a pending re-drain and its backoff (the retired ladders are re-opened elsewhere).
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: UpdateSessionOptionalsignal: AbortSignalPrivatewithRun a worker-backed step under signal AND this loader's dataset signal
(dataset, the current one by default), so disposing the dataset settles
it even when no per-update signal was given (initial build, retry). The
worker pool is a page singleton shared by every host, so the dataset
signal travels with the call rather than living on the pool.
PrivatecommitCommit 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: UpdateSessionOptionalsignal: AbortSignalPrivatecommitCommit 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. Mesh
buffers are not pooled, but each commit registers their resident bytes for
the shared GPU budget.
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.
PrivategetPrivateclearPrivatefinishRelease the retry's lock and resume the view: run what queued meanwhile (including a resync parked during the retry), else reload the whole view after a cleared archive fault, else resume any ladder the retry left short.
Check 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
when the last host releases its lease via releaseWorkerPool()
(LuxarApp in core/app/lifecycle/dispose-pipeline.ts, or LuxarLayer).
Main scene loader that handles the complete loading pipeline.
Features:
Lifecycle: one-shot. A loader loads exactly one dataset: a second
loadScene()— or one afterdispose(), which is terminal — throws. UseSceneLoaderManager.createLoaderAsync()(the canonical entry point), which awaits the previous loader's teardown and constructs a fresh loader per dataset.