Skip to content

skip most draws while a replay seek is in flight - #106

Open
JBoggsy wants to merge 1 commit into
replay-controlfrom
replay-control-fast-seek
Open

JBoggsy wants to merge 1 commit into
replay-controlfrom
replay-control-fast-seek

Conversation

@JBoggsy

@JBoggsy JBoggsy commented Oct 11, 2026

Copy link
Copy Markdown

Skip most draws while a replay seek is in flight

Depends on #102 (stacked on replay-control).

What and why

A cold seek to the end of a 23,642-tick GotA replay took 14.5 s at the 33 ms embed slice and 28 s at 10 ms. Each seek frame spent about as long drawing as simulating, then waited for the next display refresh. Pure simulation is about 8–9 s of that.

  • GotA: while a seek is in flight, draw at most every SeekDrawSeconds (0.25 s) so the view still moves. Other frames only simulate, skip present, and the main loop yields without waiting for requestAnimationFrame. The frame that lands or cancels a seek is drawn as before, so the final frame is exact.
  • replaycontrols.nim: publishReplayControlFrame(..., drawn). An undrawn frame reports its tick (seek progress) but keeps the last drawn camera matrix and size, so project/pick still match the pixels on screen. Frames carry drawn to the page.

Measurements

Same harness before and after: embedded viewer, cold seek to the last tick of the 23,642-tick replay, then cancel during a fresh long seek; host page runs a 10 ms timer. Headless Chromium with Metal, M-series Mac:

Slice Seek before → after Host timer gap p90 before → after Cancel after
10 ms 28.1 s → 11.7 s 38 → 23 ms 54 ms
20 ms 18.9 s → 9.8 s 51 → 47 ms 30 ms
33 ms (embed default) 14.5 s → 9.0 s 62 → 75 ms 27 ms
100 ms 10.2 s → 9.1 s 132 → 224 ms 23 ms

The browser test's 28,909-tick seek at 33 ms went from 16.5–17.2 s to 11.3–11.7 s. In PlayerADE's headed workbench (heavier page) the same tape went 42.6 → 14.4 s at 10 ms, 25.4 → 11.7 s at 20 ms, 18.9 → 10.3 s at 33 ms, with cancel under 100 ms. Backward seeks are unchanged (~35 ms).

Trade-off: at 33 ms and above, chained undrawn frames give the host's timers slightly fewer gaps to run in (p90 62 → 75 ms at 33 ms). Short slices now cost little seek time, so hosts that care about smoothness can use 10–20 ms. I left the embed default at 33 ms.

What to look at

  • The frame-loop change re-indents the draw blocks; review with git diff -w. The real change is the frameDrawn decision after simulation, if frameDrawn: around drawing/present, and if frameDrawn: waitForDisplay() in the main loop.
  • Native (non-embedded) viewers get the same behaviour: during a long scrub or seek, the in-canvas transport bar updates four times a second instead of every frame.
  • The worst single timer gap (~250–490 ms) is still there; it is not drawing. Add a replay control API for embedded browser viewers #102's follow-up to profile it still stands.

Tests

  • nim r tests/test_replaycontrols.nim: pass, new case that an undrawn frame keeps the drawn camera for project.
  • nim r tests/tests.nim: all tests passed (with the sibling PRs merged in); nim check GotA desktop/headless/coworld: pass.
  • test_browser_with_playwright.py --control-only --gpu: pass; the cold seek check now asserts the landing frame is drawn and that at most four seek frames a second are drawn (e.g. 43 drawn of 284). Also passes on SwiftShader.

Docs: docs/replay-control.md (drawn field, new slice table).

🤖 Generated with Claude Code

Measurements of the embedded GotA viewer showed each seek frame spending
about as long drawing as simulating, plus a wait for the next display
refresh, so a cold seek to the end of a 23,642-tick replay took 14.5 s at
the 33 ms embed slice and 28 s at 10 ms.

- GotA: while seeking, draw at most every SeekDrawSeconds (0.25 s); other
  frames only simulate, skip present, and yield without waiting for the
  display. The frame that lands or cancels a seek is drawn as before.
- replaycontrols.nim: publishReplayControlFrame takes drawn; undrawn frames
  report their tick but keep the last drawn camera and size, so project and
  pick match the pixels on screen. Frames carry drawn to the page.
- Result (same harness, headless Chromium + Metal): 33 ms 14.5 s -> 9.0 s,
  20 ms 18.9 s -> 9.8 s, 10 ms 28.1 s -> 11.7 s; cancel stays under 60 ms.
- Tests: replay control case for undrawn frames; the browser cold seek
  checks the landing frame is drawn and at most four draws a second.
- Docs: docs/replay-control.md (drawn field, new slice table).

The frame-loop change re-indents the draw blocks; review with git diff -w.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant