PrivateworkersPrivateinitPrivatereadyPrivatesettleSettles readyPromise: no argument resolves, an error rejects.
PrivatenextPrivatepoolPool-wide abort signal. When set (by setAbortSignal), every
runWithTimeout call additionally races against this signal so
a dataset-switch in SceneLoader can immediately settle all
in-flight worker promises. The signal does not cancel WASM
execution — see WorkerAbortError for the trade-off.
Setting a new signal replaces (not chains) any previously-set
signal. The new signal applies to subsequent runWithTimeout
calls; already-racing promises continue with their original
signal until they settle.
PrivateinitPrivatependingPrivategetGet the configured worker count, capped by hardware concurrency
Initialize worker pool (lazy initialization)
PrivatewhenResolve as soon as the pool has ONE usable worker, instead of all of them.
The hot path's contract. initialize() keeps the all-settled one, so
anything that genuinely wants a full pool (or wants to observe the final
worker count) should keep awaiting that.
PrivateinitializeRace api.initialize() against (1) a hard init timeout and (2) the
worker's own onerror/onmessageerror events.
The pool's permanent attachWorkerErrorHandlers evicts a failed
worker via handleWorkerFailure, but during pool init the worker
isn't in this.workers yet — so handleWorkerFailure finds
nothing to evict and the dangling Comlink initialize() promise
never settles. (Repro: route-block the worker script in a
Playwright test; the page hangs at boot.)
We attach a short-lived listener that rejects the init promise when the worker fails before it joins the pool. The init timeout is the belt-and-suspenders fallback — even if no error event fires (e.g. a network stall that never resolves), we eventually reject and let the caller fall back to the main thread.
OptionalwasmPath: stringPrivateattachInstall onerror and onmessageerror handlers on a freshly-created
worker so a crash inside the worker (uncaught throw, OOM during WASM
init, unserializable Comlink message) surfaces as a logged failure
and is removed from the active pool, rather than escaping to the
browser's window.onerror and freezing requests that are awaiting
Comlink replies from this worker.
Note: this is a best-effort safety net. Comlink-wrapped calls that are mid-flight when the worker dies will still hang their callers — route hot paths through runWithTimeout for the per-call timeout that complements this handler.
PrivatehandleRemove a failed worker from the pool and terminate it. If this drops the pool to zero workers, log a clear error so the surrounding application can decide whether to fall back to the main-thread implementation or surface a failure dialog.
Explicitly reset the pool's cached init promise so the next getWorker / runWithTimeout call re-runs init. Used to recover from a transient init failure (e.g. the worker chunk was briefly unreachable) without restarting the entire app.
Routine "init failed once, fall back" cases should NOT call this — letting the rejected promise stick keeps subsequent calls fast (instant reject) instead of stacking 10s init guards.
Race a worker-routed Promise against a timeout. On timeout, log
the failure, evict the responsible worker (if known) via
handleWorkerFailure, and reject with
WorkerTimeoutError. A timeoutMs of 0 disables the
timeout — callers that don't need it can still use this helper as
a thin pass-through to keep the call-site uniform.
worker may be omitted when the caller cannot identify which
worker handled the call (e.g. round-robin selection); the timeout
still fires, but the pool isn't pruned.
Optionalworker: WorkerPrivatenextPick the next round-robin WorkerInstance. Internal — exposes the
underlying Worker so runWithTimeout can evict it on timeout.
Get a worker API using round-robin selection.
Untracked callers receive workers in rotating order. For load-aware selection (picking the worker with the fewest active queries), use getWorkerWithTracking instead.
Important — no timeout guard. Direct await on the returned
Remote<DataWorkerAPI> lets a dead/stuck worker hang the caller
indefinitely (Comlink's onerror handler can't settle an in-flight
promise). Production code MUST go through runWithTimeout
instead — it routes the call through withTimeout on a
round-robin-selected worker. This direct getWorker() accessor is
intentionally retained for tests and low-level worker-pool
unit tests that need raw access; lint-grep for new production
uses periodically.
PrivatepickPick the kind-appropriate timeout from config. Projection AND
decode share workerProjectionTimeoutMs (both are long-running
CPU-bound calls — a dedicated decode knob can be added later if
telemetry shows a need).
Run a worker call through withTimeout on a round-robin-selected
worker. The single production entry point for any Comlink-routed call
that needs a hang-detection guard — direct await against a worker
Remote lets a dead worker hang the caller forever (the onerror
handler can't settle a Comlink promise that's already in flight).
On timeout the responsible worker is evicted via
handleWorkerFailure and the caller receives a
WorkerTimeoutError. The geometry loaders deliberately do NOT
run the projection in-process on a timeout (see
isWorkerInfrastructureError) — the failure is recorded and the node
retried against a fresh worker instead of blocking the UI thread.
Optionalsignal: AbortSignalSet or clear the pool-wide abort signal. Every subsequent
runWithTimeout call additionally races against this
signal. Used by SceneLoader.loadScene to immediately settle
worker tasks queued by the previous dataset when the user
switches datasets.
Pass undefined to clear (no pool-wide signal — only caller-
supplied signals apply).
Setting a new signal replaces (not chains) any previously-set signal. Already-racing promises continue with their original effective signal until they settle.
PrivatecombineCombine the pool-wide signal with a caller-supplied signal into
a single scoped abort source. Returns undefined when both are
absent. Uses native AbortSignal.any when available (modern
browsers / Node 20+) and falls back to the simpler "trip either
one" wiring for older runtimes; the returned scope's dispose()
releases the fallback's source listeners once the call settles.
Get a worker with query tracking for load balancing.
Returns the worker api, the underlying worker (for
eviction-on-timeout), and callbacks to mark query start/end so
the active-queries counter reflects in-flight load.
Used by runWithTimeout to route hot-path calls to the least-loaded worker. A stalled worker stops being selected once its activeQueries grows past the others — without this, the round-robin fallback would queue new calls on the stalled worker until it timed out individually.
Get the number of active workers
Get pool statistics for monitoring
Convenience accessor for the aggregate "in-flight task" count.
Exposed in __luxarDebug.workers.queueDepth for live diagnostics
of prefetch backpressure / dataset-switch task accumulation.
The number of worker tasks currently in flight across the
pool (sum of activeQueries per worker). Zero when the pool is
idle.
Check if worker pool is initialized
Clean up all worker resources.
Also terminates workers still pending init (via pendingWorkers)
and bumps initGeneration so any factories still in flight detect
the dispose and self-terminate when they resolve.
"At least ONE worker is usable" gate, settled as soon as the first worker is published — rejected only when the attempt ends with none.
Distinct from initPromise, which keeps its all-settled meaning. The hot path awaits THIS one: a 1024-splat first LOD rung has no use for workers 2..N, and waiting for them cost 2.23 s of a measured 8.0 s first paint (workers become ready ~160 ms apart as each compiles its own copy of the WASM module).
Mirrors
initPromise's deliberate "stays rejected" contract — it is NOT nulled on failure, so a poisoned pool keeps failing fast instead of re-running the init guard per call.dispose(),reinitialize()and the'pool-empty'branch of handleWorkerFailure are the only resets.