Luxar Viewer API Documentation - v2026.10.19
    Preparing search index...

    Module data/scene-loader/prefetch/slice-prefetcher

    SlicePrefetcher — projected t+1 prefetch during dimension playback.

    While a dimension is PLAYING, refinement is suppressed (the progressive loaders report hasMoreLODs === false under a frame budget), leaving an idle window between a tick's commit and the next tick. This prefetcher uses that window to build the NEXT timepoint's decoded ladder and store it in the shared SliceCache under the t+1 view signature, so the next real tick's restoreLadder hits instantly and its whole frame budget goes to DEEPENING the prefix instead of rebuilding it. Being driven by DimensionAnimationManager.peekNextValue, it is loop/bounce/backward aware — in particular it warms the loop WRAP (t=max → t=min), which the linear-extrapolation chunk prefetch cannot predict.

    The foreground loaders CANNOT be reused for a concurrent prefetch: they hold mutable per-instance state (the per-call signal context read by the RangeLoader signal-source and the L0 proxy, a REUSED accumulator whose returned arrays the next load overwrites, loadedLODs/lastViewState in the progressive wrappers). And SceneLoader.updateView is single-flight — routing a prefetch through it would abort/supersede the foreground pass and merge t+1 into the persistent view state (stuck-display hazard).

    So each registered node gets a lazily-built SHADOW loader from the same factory helpers: own accumulator, own signal, zero shared mutable state with the foreground. The S-cache IS the handoff — the shadow's own updateView does restore→deepen→store (it carries a frameBudgetMs, which bounds how far one pass deepens ≈ one cold level and makes the progressive loaders store the deepened prefix ONCE, at the end of the pass), and the foreground restores on the real tick. Shadows are deliberately NOT monitor-connected (the factory is side-effect-free by design), so shadow loads never double-count metrics.

    When the real tick for key K arrives while K's shadow pass is still running, redoing the slice in the foreground doubled the work (measured: 49% of decodes were duplicates; the shared L0 in-flight map now dedups the decode itself, but dequant + assembly would still run twice). So each shadow pass registers an in-flight store for its key in the S-cache (beginShadowStore), released when the pass ends; a foreground pass for K awaits it (awaitShadowStore, bounded, abortable by its own update signal) and then restores the shadow's stored ladder. The registration lives in the S-cache rather than on this class because the foreground loaders already hold the S-cache and compute the exact same key; they know nothing about the prefetcher.

    Shadow stores are PINNED (protected from eviction until a consuming get), so a t+1 entry survives until its tick. A pin the foreground never consumes — a departure re-store of an already-shown frame, a skipped frame, the frame after the last tick — would otherwise stay pinned forever (measured: 513 entries / 490 MB, 29% of the S-cache, after h2afva playback stopped). The prefetcher records every key its shadows may have pinned (each shadow pass's key; a departure store's key is that shadow's PREVIOUS pass key) and unpins them all in SlicePrefetcher.releaseShadows (playback end) and SlicePrefetcher.dispose.

    A cold LOD level takes longer than one playback frame to decode, so the shadow deepen must NOT be aborted every tick — the foreground therefore does NOT preempt it. Instead prefetch is a no-op while a batch is still in flight (see inFlight): the running deepen completes and caches its level, and the next FREE tick re-targets at the then-current predicted view. Across playback loops this fills the SliceCache toward full ladders, so cached quality climbs while the foreground stays responsive.

    This is safe because the shadow runs on its own loader instances (own accumulator + signal), decodes on the worker pool, and only writes the shared SliceCache under content-keyed entries — it cannot stall or corrupt a foreground tick. prefetch() is fire-and-forget (never awaited; every rejection, including expected AbortErrors, is swallowed); its fetches share the global 24-slot data fetch lane and are bounded by the budget + abort. abortInFlight() / releaseShadows() (playback end) / dispose() tear it down; only the last two release the pins.

    SlicePrefetcher
    SlicePrefetcherCtx