feat(core): add Deck#waitForFrameReady() Promise API#10362
Conversation
Adds a public method on the Deck class that resolves once all pending
updates have settled and a frame has been rendered. Useful for headless
capture, video export, and any flow that needs to know the next canvas
read will reflect a fully-settled scene.
Settled means:
- All layers report `layer.isLoaded === true`
- LayerManager.needsUpdate() returns false
- Deck.needsRedraw() returns false
- An onAfterRender cycle has completed since the call started
(skipped when the scene is already settled at call time)
Options:
- timeout (default 5000 ms): rejects with an Error if exceeded
- checkLayers (default true): include the per-layer isLoaded check
- checkAttributes (default true): include the LayerManager.needsUpdate check
The implementation chains a one-shot frame-completion handler over the
user-provided onAfterRender and restores the original handler before
resolving or rejecting, so existing render hooks continue to fire.
Motivating use case: noodles.gl video export currently relies on a
"skip first render + 16ms timeout" heuristic to avoid stale data; a
deterministic Promise API lets the exporter wait for actual readiness.
Tests cover the happy path, async-data settle, timeout rejection,
checkLayers=false escape hatch, the not-initialized error case, and
that the user-provided onAfterRender is preserved across the wait.
| * A Promise that resolves with an object describing the final state: | ||
| + `layersReady` (boolean) - whether all layers reported loaded | ||
| + `attributesReady` (boolean) - whether the attribute manager reported settled | ||
| + `duration` (number) - elapsed time in milliseconds |
There was a problem hiding this comment.
Do the majority of applications need duration? Can't they measure this trivially if they want it?
| If the deadline passes before the scene settles, the Promise rejects with an `Error`. | ||
|
|
||
| ```ts | ||
| const result = await deck.waitForFrameReady({timeout: 5000}); |
There was a problem hiding this comment.
This is a good pattern, but I don't see why this needs to be a part of deck's API.
Wouldn't a utility function with deck as a parameter be just as effective?
|
@chrisgervang: @Pessimistress - this PR adds |
Summary
Adds a waitForFrameReady() Promise API to Deck for detecting when a frame is safe to capture during sequential rendering (video export, image sequences, headless rendering).
Motivation
When exporting video from deck.gl applications (e.g., noodles.gl), detecting when a frame is "ready to capture" is critical for correctness. The existing onAfterRender callback fires after GPU command submission but doesn't wait for:
This leads to captured frames with stale/incomplete data. Polling onAfterRender with debounced timeouts works but is slow (~300ms per frame). This PR adds a deterministic signal for frame readiness, achieving 8.6× faster video export (308ms→36ms/frame) in noodles.gl.
API
Resolves when:
Rejects if timeout is reached before the above conditions are met.
Usage Example
Implementation Details
Tests
6 new tests in test/modules/core/lib/deck.spec.ts:
All 27 deck.spec.ts tests pass. All 272 core module tests pass.
Performance
Less than 1ms overhead per frame when layers are already loaded (single RAF + state check).
Limitations
Does not check for in-progress animations (layer transitions, viewport easing). For video export use cases, disable animations:
Or use the onFrameComplete callback (#10361) which provides animationsInProgress flag.
Breaking Changes
None - purely additive API.
Related PRs
Companion MapLibre PRs
Benchmarks
Tested in noodles.gl video export (30fps, 100 frames, cached tiles):
Co-authored-by: Claude Sonnet 4.5 noreply@anthropic.com