Simple Timers Service
Here is a Simple Timers Service that can manage multiple timed events -- feel free to use it in your games :D
How to use
This service is a Singleton and can be referenced with the variable "timer".
The service exposes 2 methods:
- timer:start( _frames , _callback )
- Simply start any number of timers, wherever you want in your code
- _frames: the number of frames to count down before executing the callback function
- _callback: the function to execute after the number of frames has elapsed
- Example: "timer:start(600,my_function)" -- this will execute "my_function()" after 600 frames have elapsed
- timer:update()
- Simply place this into the pico _update or _update60 function to allow the service to update itself each frame
- Note that update60 will cause the timer to "count" twice as fast, since it is counting down the number of frames
- The service uses this internally to iterate all of the timers that have been started in order to decrement each / execute callbacks as necessary
The code
In the cart, Tab 0 is the Simple Timers Service code. Tab 1 is a commented example of how to use it in your project. I have pasted the code below for your convenience:
Tab 0 - Simple Timers Service
--simple timers service --by buck young | professir timer=(function() local sts={ update=function(e) for i=1,#e.l do e.l[i].t-=1 if e.l[i].t<=0 then e.l[i].f();deli(e.l,i) return end end end, start=function(e,t,f) add(e.l, {t=t,f=f}) end, } sts.__index=sts return setmetatable({l={}},sts) end)() |
Tab 1 - Example Implementation
-- example implementation function _init() cls(0) -- start a timer -- from anywhere in your -- code. this code says -- "after 30 frames, call -- the 'green_screen' -- function": timer:start(30,green_screen) -- you can even in-line -- an anonymous function. -- this call says "after -- 60 frames, turn the -- screen red": timer:start(60,function() cls(8) end) -- you can start as many -- timers as you like, -- they are all managed -- within the service itself. timer:start(90,green_screen) end function _update() -- be sure to include this -- in your update function -- so the timers service -- can do its job: timer:update() end function green_screen() cls(11) end |
Many thanks to FReDs72 and merwok for some helpful discussions in the PICO-8 discord!
Can you reiterate a timer, @professir ? That is ...
timer:repeatcycle(30,green_screen) |
So every 30-cycles green_screen would be called, not just once but every 30-cycles until the program is stopped or the timer is removed.
And do you have the ability to forcefully remove a timer that is either active or in a loop ?
if btnp(4) then timer:stopcycle(green_screen) end |
While we're on the same subject, can you retrieve or set an ID # for that particular timer event ?
if mode==0 then a=timer:repeatcycle(30,green_screen) mode=1 elseif mode==1 then if btnp(4) then timer:stop(a) end end |
Thanks for the suggestions! :D
You could certainly repeat a timer indefinitely -- or even conditionally decide to repeat it -- by starting a timer within a function that calls back to itself:
function foobar() -- do stuff -- optionally: if something then... timer:start(30, foobar) end |
I'll keep your other feature suggestions in mind & may add them if I find the need in a future project. Currently, this service covered all my timing needs :)
Hi @professir:
No, what I mean is you could do something like this:
function _init() timer:repeat(30,grandfatherclock) end function _update() end function grandfatherclock() sfx(1) end |
So in running it you would get a nice click every second. No need to reiterate it in your _update() each time.
If this can already be done, where the call to TIMER is in init() and repeats, please show me how.
Thanks !
The service requires update to be called each frame, otherwise the list of timers that is maintained by the service will not count down.
To accommodate your use-case, the code would have to be written like this:
function _init() grandfatherclock() -- this starts the grandfather clock end function _update() timer:update() -- this is required by the service, otherwise no timer will trigger end function grandfatherclock() sfx(1) timer:start(30,grandfatherclock) -- this repeats the tick every 30 frames end |
The service can only start timers and call a function when a timer has finished. There is no other functionality to the service - like stopping or cancelling.
Hope this helps!
Note: if you did want to stop ticking the grandfather clock, you could do something like this
function _init() should_repeat=true -- change this to false when you want to stop the repeating timer grandfatherclock() end function _update() timer:update() end function grandfatherclock() sfx(1) if should_repeat then -- consider if you should continue ticking timer:start(30,grandfatherclock) end end |
Yeah ... that's definitely not what I had in mind, @professir.
Interesting though how it can be done. Might be the limitations of the system.
At some point I may try and dissect your brainchild there and see if there's some way to just have in _update() just timerupdate(), and nothing else.
From there it would automatically run every task or function that's already scheduled on the clock from init() or other run-once functions.
I know you can do this in Blitz with some interrupts. I know for certain it could be done easily on the Commodore Amiga - it was all about writing software with interrupts.
> At some point I may try and dissect your brainchild there and see if there's some way to just have in _update() just timerupdate(), and nothing else.
> From there it would automatically run every task or function that's already scheduled on the clock from init() or other run-once functions.
I must be misunderstanding you, but this is exactly what the timers service does :D
Hi @professir:
Let me see if I can lay it out better. For both of us really. :)
I want to be able to create a timer event in _init() called let's say grandfatherclock. Grandfatherclock itself has nothing except to play a single sound effect.
Inside update() there is only one command and that is updateclock().
function _init() addtimerevent(grandfatherclock,30) end function _update() timerupdate() end function grandfatherclock() sfx(1) end |
Now is this what your timer does ? Or can it do this - with the function of grandfatherclock() having ONLY the single line in it ?
... I'm reading your code proper now, @professir. I am not seeing DEL(). OK I see it - you have one line of code waaay long that has it. :) Let me add some CRs in there and try to make heads or tails out of what you're doing.
I'm suspecting if you remove the DELI() then the timers will run irregardless of being "boosted" each time.
. . . ???
OK by removing the DELI() (Delete Index) it then just continues to run it in a tight loop with no timing. So - it does look like it's possible to reinstate its timer so it runs dutifully once every second without manual reiteration of that specific function via _UPDATE(). Yes ?
BTW here is the code expanded (I think ...) there may still be still some multiple definitions all on one line. It's very advanced coding you did here.
--simple timers service --by buck young | professir timer=(function() local sts={ update=function(e) for i=1,#e.l do e.l[i].t-=1 if e.l[i].t<=0 then e.l[i].f() deli(e.l,i) return end end end, start=function(e,t,f) add(e.l,{t=t,f=f}) end,} sts.__index=sts return setmetatable({l={}},sts) end)() |
> Now is this what your timer does ? Or can it do this - with the function of grandfatherclock() having ONLY the single line in it ?
Very simply, no - the service only has a "start" and "update" function. But working with the service -- and perhaps changing the way you are thinking about it -- allows you to accomplish this goal with a single line difference as explained via the code here https://www.lexaloffle.com/bbs/?pid=100516#p
> try to make heads or tails out of what you're doing.
All this service does is append an object to a table when you call "start". The object has two properties: 1) a number of frames to wait (t) and 2) a function to call when the number of frames reaches 0 (f). On every update call (thus, every frame), the service loops through all of the objects that were added to the table and decrements each object's "t" by 1 (thus, the timers count down). Once "t" reaches 0 for any object, the object's "f" function is called and that object is removed from the table since the timer is considered done.
> by removing the DELI()
Yes, if you really want to add repeating functionality to the service, you will need to conditionally delete the object from the table -- but you will also need to reset the number of frames (t) back to the initial value passed into "start" (since the "t" value is mutable as it counts down to 0).
EDIT: Feel free to DM me in discord - or, of course, continue responding here - if you'd like to chat any more about this
I don't follow the usage. How do you implement this into the drawloop?
It's requiring me to do
function _draw() if not switch then cls(0) timer:start(..) switch=true end |
It doesn't work if there is a cls() right after _draw()...so I don't follow how or what the point is if it requires a switch when used in the drawloop and won't allow the usage of cls(). Is this meant for people not using the drawloop? Can this be modified to be used with the basic loop? i.e.
function _update() end function _draw()cls() end |
What are you attempting to do?
Most likely, you wouldn’t want to start a timer every single time _draw is executed. This will create a large number of timers very quickly.
[Please log in to post a comment]