Skip to content

Commit 417e33d

Browse files
committed
Make sure we call settimerevent when adding and removing timers... duh...
1 parent aaa6277 commit 417e33d

File tree

3 files changed

+69
-15
lines changed

3 files changed

+69
-15
lines changed

VM/src/llltimers.cpp

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ int timer_event_wrapper_cont(lua_State *L, [[maybe_unused]] int status)
4343
// Forward declarations for timer wrapper management
4444
static void register_timer_wrapper(lua_State *L, lua_LLTimers *lltimers);
4545
static void unregister_timer_wrapper(lua_State *L, lua_LLTimers *lltimers);
46+
static void schedule_next_tick(lua_State *L, lua_LLTimers *lltimers);
4647

4748
static void lltimers_dtor(lua_State *L, void *data)
4849
{
@@ -144,12 +145,15 @@ static int lltimers_on(lua_State *L)
144145
// Add to the end of the timers array
145146
lua_rawseti(L, -2, old_len + 1);
146147

147-
lua_pop(L, 1); // Pop timers array
148-
149148
// Register timer wrapper with LLEvents when adding first timer
150149
if (old_len == 0)
151150
register_timer_wrapper(L, lltimers);
152151

152+
// Reschedule timer tick since we added a new timer
153+
schedule_next_tick(L, lltimers);
154+
155+
lua_pop(L, 1); // Pop timers array
156+
153157
// Return the passed-in handler so it can be unregistered later
154158
lua_pushvalue(L, 3);
155159
return 1;
@@ -190,6 +194,10 @@ static int lltimers_once(lua_State *L)
190194

191195
// Add to the timers array
192196
lua_rawseti(L, -2, old_len + 1);
197+
198+
// Reschedule timer tick since we added a new timer
199+
schedule_next_tick(L, lltimers);
200+
193201
lua_pop(L, 1); // Pop timers array
194202

195203
// Register timer wrapper with LLEvents when adding first timer
@@ -247,10 +255,11 @@ static int lltimers_off(lua_State *L)
247255

248256
lua_pop(L, 1); // Pop timers array
249257

250-
// Check if we just removed the last timer
251-
if (found && len == 1)
258+
// Reschedule timer tick since we may have removed a timer
259+
// (this will also unregister the wrapper if we removed the last timer)
260+
if (found)
252261
{
253-
unregister_timer_wrapper(L, lltimers);
262+
schedule_next_tick(L, lltimers);
254263
}
255264

256265
lua_pushboolean(L, found);
@@ -313,23 +322,26 @@ static void unregister_timer_wrapper(lua_State *L, lua_LLTimers *lltimers)
313322
}
314323

315324
// Helper function to schedule the next timer tick
316-
// Expects timers_tab to be on the stack at the given index
317-
static void schedule_next_tick(lua_State *L)
325+
// Accepts LLTimers userdata as parameter and manages its own stack
326+
static void schedule_next_tick(lua_State *L, lua_LLTimers *lltimers)
318327
{
319328
auto *sl_state = LUAU_GET_SL_VM_STATE(lua_mainthread(L));
320329

321330
if (!sl_state->setTimerEventCb)
322331
return; // No callback set, can't schedule
323332

324-
int len = lua_objlen(L, LIVE_TIMERS_ARRAY);
333+
// Get timers array on the stack
334+
lua_getref(L, lltimers->timers_tab_ref);
335+
int len = lua_objlen(L, -1);
325336

326337
if (len == 0)
327338
{
339+
lua_pop(L, 1); // Pop timers array
340+
328341
// No timers pending, unsubscribe from the parent timer event
329342
sl_state->setTimerEventCb(L, 0.0);
330343

331344
// Unregister timer wrapper from LLEvents
332-
auto *lltimers = (lua_LLTimers *)lua_touserdata(L, LLTIMERS_USERDATA);
333345
unregister_timer_wrapper(L, lltimers);
334346

335347
return;
@@ -339,13 +351,15 @@ static void schedule_next_tick(lua_State *L)
339351
double min_time = HUGE_VAL;
340352
for (int i = 1; i <= len; i++)
341353
{
342-
lua_rawgeti(L, LIVE_TIMERS_ARRAY, i);
354+
lua_rawgeti(L, -1, i); // Get timer from timers array
343355
lua_rawgeti(L, -1, TIMER_NEXT_RUN);
344356
double next_run = lua_tonumber(L, -1);
345357
lua_pop(L, 2); // Pop nextRun and timer data
346358
min_time = fmin(min_time, next_run);
347359
}
348360

361+
lua_pop(L, 1); // Pop timers array
362+
349363
// Get current time
350364
double current_time = sl_state->clockProvider ? sl_state->clockProvider(L) : 0.0;
351365

@@ -589,7 +603,8 @@ static int lltimers_tick_cont(lua_State *L, [[maybe_unused]]int status)
589603
}
590604

591605
// Done processing all timers, schedule the next tick
592-
schedule_next_tick(L);
606+
auto *lltimers = (lua_LLTimers *)lua_touserdata(L, LLTIMERS_USERDATA);
607+
schedule_next_tick(L, lltimers);
593608

594609
return 0;
595610
}

tests/SLConformance.test.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,9 +603,11 @@ TEST_CASE("LLEvents")
603603
}
604604

605605
static double test_clock_time = 0.0;
606+
static double last_timer_interval = -1.0;
606607
TEST_CASE("LLTimers")
607608
{
608609
test_clock_time = 0.0;
610+
last_timer_interval = -1.0;
609611
runConformance("lltimers.lua", nullptr, [](lua_State *L) {
610612
lua_pushcfunction(L, lua_break, "breaker");
611613
lua_setglobal(L, "breaker");
@@ -624,15 +626,23 @@ TEST_CASE("LLTimers")
624626
}, "getclock");
625627
lua_setglobal(L, "getclock");
626628

629+
// Provide a function to read the last timer interval
630+
lua_pushcfunction(L, [](lua_State *L) {
631+
lua_pushnumber(L, last_timer_interval);
632+
return 1;
633+
}, "get_last_interval");
634+
lua_setglobal(L, "get_last_interval");
635+
627636
auto sl_state = LUAU_GET_SL_VM_STATE(L);
628637
// Set up clock callback to return our test time
629638
sl_state->performanceClockProvider = sl_state->clockProvider = [](lua_State *L) {
630639
return test_clock_time;
631640
};
632-
// Set up timer event callback (no-op for tests)
641+
// Set up timer event callback to capture the last interval
633642
sl_state->setTimerEventCb = [](lua_State *L, double interval) {
634643
// In real usage, this would schedule a timer event
635-
// For tests, we manually call _tick()
644+
// For tests, we manually call _tick() and capture the interval
645+
last_timer_interval = interval;
636646
};
637647
});
638648
}

tests/conformance/lltimers.lua

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ assert(typeof(on_handler) == "function")
2828
-- Simulate timer tick
2929
setclock(0.05) -- Not time yet
3030
-- `ll.GetTime()` should be using the clock provider.
31-
assert((ll.GetTime() - 0.05) < 0.01)
31+
assert(math.abs(ll.GetTime() - 0.05) < 0.01)
3232
LLTimers:_tick()
3333
assert(on_count == 0)
3434

3535
-- This should be using our fake clock
36-
assert(os.clock() - 0.05 < 0.000001)
36+
assert(math.abs(os.clock() - 0.05) < 0.000001)
3737

3838
incrementclock(0.05) -- Advance to past 0.1, should fire now
3939
LLTimers:_tick()
@@ -367,4 +367,33 @@ LLTimers:off(throw_error)
367367
LLTimers:_tick()
368368
assert_errors(function() timers_clone:_tick() end, "called!")
369369

370+
-- Test that setTimerEventCb is called with correct intervals
371+
-- Single timer should schedule with correct interval
372+
setclock(10.0)
373+
local interval_timer1 = LLTimers:every(0.5, function() end)
374+
assert(math.abs(get_last_interval() - 0.5) < 0.001)
375+
376+
-- Adding an earlier timer should reschedule to shorter interval
377+
local interval_timer2 = LLTimers:on(0.3, function() end)
378+
assert(math.abs(get_last_interval() - 0.3) < 0.001)
379+
380+
-- Adding a later timer should not change the interval
381+
local interval_timer3 = LLTimers:on(1.0, function() end)
382+
-- Should still be 0.3 (the earliest timer)
383+
assert(math.abs(get_last_interval() - 0.3) < 0.001)
384+
385+
-- Scenario 4: Removing the earliest timer should reschedule to next timer
386+
LLTimers:off(interval_timer2)
387+
-- Now earliest should be 0.5
388+
assert(math.abs(get_last_interval() - 0.5) < 0.001)
389+
390+
-- Scenario 5: Removing all timers should call with 0.0
391+
LLTimers:off(interval_timer1)
392+
LLTimers:off(interval_timer3)
393+
assert(math.abs(get_last_interval() - 0.0) < 0.001)
394+
395+
-- Oh also we should make sure that `:once()` behaves correctly.
396+
LLTimers:once(0.5, interval_timer1)
397+
assert(math.abs(get_last_interval() - 0.5) < 0.001)
398+
370399
return "OK"

0 commit comments

Comments
 (0)