Pausing a timer multiple times causes inconsistent behavior

Calling playdate.timer:pause() multiple times on a timer causes inconsistent behaviour when it is reset and started again afterwards.
Sometimes it will finish immediately and call the callback, sometimes it will resume from some seemingly arbitrary point instead of the initial duration.

i.e. this works fine:

local t = playdate.timer.new(1000, nil)
t:pause()
t:reset()
t:start()

and this causes all kinds of problems:

local t = playdate.timer.new(1000, nil)
t:pause()
t:pause()
t:reset()
t:start()

This problem happens in the simulator and on the device!

I have attached a sample project. Press A to pause the timer, press B to reset and start it. timeLeft will be printed to the developer console.
Try pressing A multiple times while the timer is running (2x, 3x, etc.) before pressing B and see how it reacts differently. And while it is in this broken state, restarting it will always repeat the broken state. Pausing it again will "fix" the behaviour.
It will print something like:

timeleft: 240
timeleft: 240
timeleft: 240
timeleft: 1000
timeleft: 40
timeleft: 10
timer has ended
timeleft: 0
timeleft: 0
timeleft: 0
timeleft: 1000
timeleft: 40
timeleft: 10
timer has ended

Expected result:
The timer should not care for multiple pause calls and always reset to and start with its set duration.
The sample project should always print something like

timeleft: 1000
timeleft: 967
timeleft: 934
timeleft: 901
timeleft: 869
timeleft: 836
timeleft: 803
timeleft: 769
timeleft: 736
timeleft: 703
timeleft: 670
... approximately linearly down to 0

playdate-test.zip (24.2 KB)

Looking at the timer.lua code in the SDK, I would propose this fix:
Insert the following code on line 210 (in playdate.timer:pause())

if self.paused then return end

I can see now why the bug happens. However to me this still is unexpected behaviour. I hardly see a reason to overwrite the pauseOffset deliberately. And always having to check the paused state before calling :pause seems inconvenient.

EDIT: Thinking about this some more, pauseOffset should probably be reset to 0 in playdate.timer:reset() as well. When I call reset, I don't expect the timer to start with an offset.
Also looking at the code, calling :start multiple times with pauseOffset != 0 could also be problematic. Maybe this should be prevented with an early exit as well...

1 Like

Nice find! I posted earlier about having an issue trying to create timers paused... Your find smells like it might be the root cause.