Skip to content

Commit a244335

Browse files
committed
Correct LLTimers catchup logic to guarantee minimum interval
This prevents timers scheduled with `LLTimers:every()` from running "too fast" for a tick after first waking back up from the script being disabled.
1 parent ffe7276 commit a244335

File tree

2 files changed

+37
-0
lines changed

2 files changed

+37
-0
lines changed

VM/src/llltimers.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,9 @@ static int lltimers_tick_cont(lua_State *L, [[maybe_unused]]int status)
615615
{
616616
new_next_run = next_run + (intervals_to_skip * interval);
617617
}
618+
// Ensure next tick is at least one full interval from now
619+
// This prevents "fast ticks" when waking up close to a catchup boundary
620+
new_next_run = std::max(new_next_run, start_time + interval);
618621
did_clamp = true;
619622
}
620623

tests/conformance/lltimers.lua

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,40 @@ assert(catchup_scheduled_times[2] < 45.2, "Second fire shows synced schedule (~4
644644
-- Clean up
645645
LLTimers:off(catchup_handler)
646646

647+
-- Test minimum interval guarantee after catchup
648+
-- After waking from a long sleep near a catchup boundary, the SECOND tick should
649+
-- never fire faster than the specified interval. Regression test for "overcorrection" bug.
650+
setclock(0.0)
651+
local min_interval_fires = 0
652+
local min_interval_times = {}
653+
local min_interval_handler = LLTimers:every(1.0, function(scheduled_time)
654+
min_interval_fires += 1
655+
table.insert(min_interval_times, getclock())
656+
end)
657+
658+
-- Timer created at T=0, first scheduled for T=1.0
659+
-- Simulate script disable/re-enable: jump forward 5.5s (past 2s catchup threshold)
660+
setclock(5.5)
661+
LLEvents:_handleEvent('timer')
662+
assert(min_interval_fires == 1, "First tick should fire immediately after wake")
663+
664+
-- The second tick should be at least 1.0s from now, not at T=6.0
665+
setclock(6.0) -- Only 0.5s later
666+
LLEvents:_handleEvent('timer')
667+
assert(min_interval_fires == 1, "Second tick should NOT fire at T=6.0 (only 0.5s after first)")
668+
669+
-- Should fire at T=6.5 or later (1.0s after T=5.5)
670+
setclock(6.51)
671+
LLEvents:_handleEvent('timer')
672+
assert(min_interval_fires == 2, "Second tick should fire at ~T=6.5 (1.0s after first)")
673+
674+
-- Verify the gap between fires was at least the interval
675+
local gap = min_interval_times[2] - min_interval_times[1]
676+
assert(gap >= 1.0, `Gap between ticks should be >= 1.0s, got {gap}`)
677+
678+
-- Clean up
679+
LLTimers:off(min_interval_handler)
680+
647681

648682
-- Verify that user code can't get a reference to the timers tick function through `debug`
649683
local expected = {

0 commit comments

Comments
 (0)