PrivateconstructorPrivate constructor to enforce singleton pattern
Private StaticinstancePrivateloadersPrivatedefaultPrivate ReadonlyprofilerUpdate profiler for timing scene updates Singleton owned by the manager, shared with all loaders
PrivatemonitorOptional monitor factory injected by core/app.ts so each
SceneLoader we create can resolve a UI monitor without the
data/ layer importing ui/. Null means "no UI monitor wired
up" (tests / embedders) and SceneLoader treats every monitor
call as a no-op.
PrivatelodOptional LOD-group registry factory. Same dependency-inversion
pattern as monitorFactory — the app pipeline owns the live
SceneManager (camera, viewport) and closes over it to build a
registry per loader without the data/ layer importing scene/.
PrivaterequestOptional render-loop wake-up forwarded to every created loader
(→ SceneLoader.setRequestRender). Same dependency-inversion
pattern as monitorFactory: the app pipeline wires it to
AnimationController.startAnimation so late geometry commits
(refinement, retries, lazy LOD loads) repaint after the rAF loop
has idle-paused. Null (tests / embedders) means commits never
wake a loop — SceneLoader treats it as a no-op.
PrivateautoPrivaterefinementProjected-density provider + caps for the refinement rung gate, forwarded
to every created loader (→ SceneLoader.setRefinementDensityProvider).
Null provider = bytes-only admission.
PrivatedecodeProvide the monitor factory. Called once at app boot from
core/app.ts (which holds the DataMonitorManager reference).
Subsequent createLoader calls forward the factory to each
SceneLoader instance.
Provide the LOD-group registry factory. Called once at app boot
from the init pipeline (which holds SceneManager + the live
camera). Subsequent createLoader calls forward the factory
to each SceneLoader instance.
Provide the render-loop wake-up callback. Called once at app boot
from the init pipeline; forwarded to each subsequently created
SceneLoader (see the requestRender field).
Forward transient-failure notifications to every current and future loader.
Provide the projected-density source for the refinement rung gate.
Forwarded to every existing and subsequently created SceneLoader.
Get the update profiler instance Used by DataLoadingMonitor to display timing data
StaticgetGet the singleton instance of SceneLoaderManager
Create a new SceneLoader instance
Unique identifier for this loader
Optionalconfig: LoaderConfig
Optional loader configuration
Whether to set this as the default loader
The created SceneLoader instance
Create a new SceneLoader, awaiting disposal of any existing loader with the same ID first.
Unlike createLoader (which fires the previous loader's async dispose without awaiting), this awaits destroyLoaderAsync so caching-store teardown and OPFS metadata flush fully drain before the replacement loader is constructed. Dataset switches must use this path so the new loader never races the old one's late teardown for cache ownership / OPFS metadata.
Unique identifier for this loader
Optionalconfig: LoaderConfig
Optional loader configuration
Whether to set this as the default loader
The created SceneLoader instance
Get a SceneLoader by ID
The loader ID
The SceneLoader instance or null if not found
Whether ANY registered loader has a LOAD PASS outstanding — an updateView
sweep (fetch/decode/upload) up to its geometry commit, or a sweep that is
queued and has not started yet. See
SceneLoader.isLoadPassInProgress for the exact scope, in particular
why the progressive-LOD refinement drain is excluded and why the queued
slot counts.
Consumed by the debug snapshot (__luxarDebug.getState().isLoading,
built in core/app/debug/debug-interface.ts), which the E2E "wait for
data" helpers poll to decide when a load has settled.
The answer is "any loader", not "the default loader", because this
manager's contract admits several: createLoader/createLoaderAsync take
an id, getAllLoaders() returns a map, and the default is merely one
elected entry. So the aggregate answers for all of them rather than
trusting the default slot to be the only busy one. (Production registers
exactly one, under 'default' — a dataset switch disposes the outgoing
loader before constructing its replacement, so the two never overlap.)
True if at least one loader is mid-load-pass; false when idle or when no loader is registered.
The progressive-refinement BYTE-ceiling stop across every registered
loader, or undefined when none of them ever declined a rung.
MERGE RULE. The FIRST loader (in registration order) that reports a stop
supplies reason, residentBytes, budgetBytes and firstPath — those
four describe one verdict at one instant and averaging or concatenating
them across loaders would describe no verdict at all. declinedPathCount
sums, because "how much of the scene stopped short" is genuinely additive,
and declinedPaths concatenates in the same order and is re-truncated to
RESIDENCY_DECLINED_PATH_SAMPLE so the merged sample cannot grow with
the loader count. So on a multi-loader page the reported verdict is the
first-registered loader's, while the count covers all of them.
Aggregated for the same reason as isAnyLoadPassInProgress: the manager's contract admits several loaders even though production registers exactly one.
Consumed by the debug snapshot (__luxarDebug.getState().refinementResidency),
which core/app/debug/capture-readiness.ts refuses a capture on.
The DEFAULT loader's GPU buffer-pool statistics, or undefined when no
loader is registered or pooling is disabled.
Deliberately NOT an aggregate, unlike refinementResidencyStop:
PoolStats carries a per-type breakdown and a largestPooledBytes figure
that do not sum, and inventing a half-merged shape for a case production
never reaches (one loader, under 'default') would be a worse answer than
a precisely-scoped one. A second registered loader's pool is therefore NOT
represented here.
Destroy a specific loader (best-effort, non-awaiting).
Synchronously removes the loader from the manager (so subsequent
getLoaderCount() / hasLoader() calls reflect the change) and
fires the async dispose without awaiting. Suits the beforeunload
path and other call sites that cannot meaningfully await teardown.
Callers that need deterministic teardown (e.g. dataset switches)
must use destroyLoaderAsync.
The loader ID to destroy
Destroy a specific loader and await its disposal.
Awaits SceneLoader.dispose so caching-store teardown, prefetcher teardown, and OPFS metadata flush all complete before this method resolves. Use during dataset switches when the next loader's init must see fully-drained state.
Destroy all loaders and reset the manager (best-effort, non-awaiting).
Mirror of destroyLoader: removes loaders synchronously and fires async disposes without awaiting. Use destroyAllAsync where deterministic teardown matters.
Destroy all loaders concurrently and await every disposal.
Promise.alls every loader's dispose() so the caller can wait for
every prefetcher, caching store, and L0 cache to drain before
proceeding. Always clears the loaders map and default-loader id,
even if individual disposals reject.
PrivatedetachSynchronously remove a loader from the manager and update the default-loader id. Returns the loader instance for the caller to dispose (sync or async). Centralizes the bookkeeping so the fire-and-forget and awaitable variants stay in sync.
Default-election contract: when the detached loader was the current
default, the next default is the FIRST remaining loader by insertion
order — i.e. the oldest loader still registered. This ordering is
a stable property of Map and therefore deterministic across reloads
for any given sequence of registrations. Callers that need a specific
default should call setDefault() explicitly rather than relying on
the implicit election.
Check if a loader exists
The loader ID to check
True if the loader exists
Get the number of active loaders
The number of active loaders
StaticdisposeDispose the current instance and clear the singleton slot.
Used at app shutdown and between tests. The next getInstance() call
lazily constructs a fresh manager.
Manager for SceneLoader instances. Provides centralized access to loader instances without global variables.