You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix frame rate limiting blocking forever when the clock cannot observe sleep
The frame rate limiting loop in flecs_insert_sleep slept a fixed
interval and remeasured until the frame reached the target time. When
the clock does not advance during a sleep, the loop never exits: a clock
that is only stepped by the host in between frames (emscripten without
asyncify, simulated clocks) blocks ecs_progress forever as soon as a
target FPS is set. A clock too coarse to observe an individual sleep
does exit, but only by spinning through many sleep and measure calls per
clock tick.
Sleep intervals now escalate when the clock is not keeping up. The clock
counts as keeping up when it advances by at least a quarter of the
interval just slept, which measures whether get_time and sleep agree
about the passage of time rather than how fast the clock runs, so a host
that virtualizes both consistently still paces correctly at any speed.
On a coarse clock, escalation grows the interval until it crosses a
clock tick, which keeps pacing working at reduced granularity. On a
clock that does not advance at all, two bounds end the frame: a stall
budget of one frame period that never refills within a frame, and an
unconditional cap of two frame periods on the total sleep requested per
frame. The budget not refilling matters, because a budget that resets
whenever the clock advances a little lets clock jitter replay the
escalation chain indefinitely on a clock that advances just enough to be
counted once per chain. The cap is denominated in requested sleep while
pacing is measured on the clock, so a clock that observes less than
about half of each sleep ends its frame proportionally short of the
target rather than sleeping longer. The ecs_set_target_fps documentation
states this contract, including the shortfall.
ecs_set_target_fps also rejects targets that are not zero or within
1e-9 to 1e9. The frame time is the reciprocal of the target, so a
subnormal target yields an infinite frame time and sleep interval, and
the value can arrive from outside the application through the REST world
summary endpoint; the range keeps the frame time finite and within
[1e-9, 1e9] by construction, rejects NaN through the comparison, and
compiles for any type ecs_ftime_t is defined to, including the integer
configuration in the custom build tests. In release builds the check
compiles out and is backstopped by ecs_sleepf, which now refuses
durations it cannot convert to the seconds and nanoseconds the OS sleep
API takes: converting a value of 2^31 seconds or more to int is
undefined behavior, and the same comparison rejects NaN. A refused sleep
returns immediately, is measured as the clock not keeping up, and rate
limiting is skipped for the frame instead of waiting.
Tests cover a stalled clock with a target FPS set, with both real and
no-op sleep functions; pacing accuracy against a sleep-driven simulated
clock at three work levels; time conservation on a coarse clock with a
target FPS; clocks just above and just below the keeping-up threshold,
which also pin that the cap ends a frame that cannot reach the target;
dither and host-stepped clocks, which bound the total sleep requested
per frame; the fps argument checks; and that ecs_sleepf never forwards
an unrepresentable duration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0 commit comments