Skip to content

Commit aa30899

Browse files
wass08claude
andauthored
fix(viewer): drive the frame loop with a timer when ?disable=draw (#483)
With no real frames submitted, Chromium's no-damage scheduler throttles requestAnimationFrame to 1Hz on Linux — measured in the headless bake worker: every useFrame system ticked once per second, so a heavy scene needed 300+ seconds of wall time just to run ~300 frames of build work (and exceeded the capture deadline on the worker's slower cores). The empty-scene clear from #482 avoids this on macOS but not on Linux. When `draw` is disabled, drive advance() with setInterval instead of rAF — plain timers are never throttled on a visible page, so the loop pacing is deterministic (fps prop) regardless of compositor heuristics. Normal rendering paths are untouched. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent bbe0e2b commit aa30899

1 file changed

Lines changed: 27 additions & 2 deletions

File tree

packages/viewer/src/components/viewer/frame-limiter.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ type FrameLimiterProps = {
66
fps?: number
77
}
88

9+
// `?disable=draw` (see post-processing.tsx): the page renders no real frames,
10+
// and Chromium's no-damage scheduler then throttles requestAnimationFrame to
11+
// 1Hz on Linux (measured in the headless bake worker — every useFrame system
12+
// ticked once per second and a heavy scene took 300+ seconds to settle). A
13+
// plain timer is never throttled on a visible page, so drive the loop with
14+
// setInterval instead of rAF when nothing is drawn.
15+
const DRAW_DISABLED =
16+
typeof window !== 'undefined' &&
17+
new Set(
18+
(new URLSearchParams(window.location.search).get('disable') ?? '')
19+
.split(',')
20+
.map((s) => s.trim()),
21+
).has('draw')
22+
923
const FrameLimiter: React.FC<FrameLimiterProps> = ({ fps = 50 }) => {
1024
const { advance, set, frameloop: initFrameloop, scene, clock } = useThree()
1125
const renderer = useThree((state) => state.gl)
@@ -18,6 +32,7 @@ const FrameLimiter: React.FC<FrameLimiterProps> = ({ fps = 50 }) => {
1832
let then = 0
1933
let i = 0
2034
let raf: number | null = null
35+
let timer: ReturnType<typeof setInterval> | null = null
2136
const interval = 1000 / fps
2237
function tick(t: DOMHighResTimeStamp) {
2338
raf = requestAnimationFrame(tick)
@@ -30,13 +45,23 @@ const FrameLimiter: React.FC<FrameLimiterProps> = ({ fps = 50 }) => {
3045
}
3146
// Set frameloop to never, it will shut down the default render loop
3247
set({ frameloop: 'never' })
33-
// Kick off custom render loop
34-
raf = requestAnimationFrame(tick)
48+
if (DRAW_DISABLED) {
49+
timer = setInterval(() => {
50+
i += interval / 1000
51+
advance(i)
52+
}, interval)
53+
} else {
54+
// Kick off custom render loop
55+
raf = requestAnimationFrame(tick)
56+
}
3557
// Restore initial setting
3658
return () => {
3759
if (raf) {
3860
cancelAnimationFrame(raf)
3961
}
62+
if (timer) {
63+
clearInterval(timer)
64+
}
4065
set({ frameloop: initFrameloop })
4166
}
4267
}, [fps, advance, set, initFrameloop, renderPaused])

0 commit comments

Comments
 (0)