Chrome stops calling requestAnimationFrame for a hidden tab, and Pixi's ticker hangs on it. The run, the drawing and the recording all stood still as soon as the user switched to another tab. A plain run keeps standing still there, so a forgotten game does not play on unheard. - backgroundBeat.ts ticks the editor from a worker at the recording's fps while a recording goes and the tab is hidden, or the page has not drawn a frame of its own for 250 ms. Once the page draws again, the beat keeps out of the way. - The worker looks every 4 ms whether a tick is due. A 16 ms interval came in bursts behind a busy page, and the recording threw away the second tick of each burst. - backgroundTimers.ts and timerWorker.ts give the short waits of a recording timers in a worker. Chrome slows the page's own timers to once a second in a hidden tab. This covers the waits while saving and the wait for a video's picture after a seek. - Behind a tab Chrome does not call requestVideoFrameCallback, but the picture is there at seeked. The wait there is 10 ms instead of 150 ms while the tab is hidden. - The beat runs for Start recording, the exported game and Start recording... in the main menu. Tests: - tsc, eslint, Prettier clean - tests/viewport: 374 green, 3 new in background-beat.test.ts - e2e 68k (3 checks) on melektaus-atx with the tab really hidden: not in real time all 120 frames, colour changes every 30 frames, the blip 8 ms from green; in real time 97 of 120 frames (73 with the tab in front) - Counter-check without the beat: all three 68k checks red, 3 frames in 60 s not in real time, 7 frames in real time - Gate on melektaus-atx: green, 311 files, 4821 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
31 lines
846 B
TypeScript
31 lines
846 B
TypeScript
/**
|
|
* The timers of `backgroundTimers.ts`: a worker's keep their pace in a hidden tab, where the page's
|
|
* own are held to once a second. `{ id, ms, repeat }` starts one, `{ id, ms: 0 }` ends it; each time
|
|
* it fires, its id comes back.
|
|
*/
|
|
const timers = new Map<number, ReturnType<typeof setTimeout>>();
|
|
|
|
self.onmessage = (event: MessageEvent<{ id: number; ms: number; repeat: boolean }>) => {
|
|
const { id, ms, repeat } = event.data;
|
|
const running = timers.get(id);
|
|
if (running !== undefined) {
|
|
clearTimeout(running);
|
|
clearInterval(running);
|
|
timers.delete(id);
|
|
}
|
|
if (ms <= 0) return;
|
|
if (repeat)
|
|
timers.set(
|
|
id,
|
|
setInterval(() => self.postMessage(id), ms),
|
|
);
|
|
else
|
|
timers.set(
|
|
id,
|
|
setTimeout(() => {
|
|
timers.delete(id);
|
|
self.postMessage(id);
|
|
}, ms),
|
|
);
|
|
};
|