Log In  

Cart #simple_timer_service-6 | 2021-11-19 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
6

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!

P#99913 2021-11-10 18:56 ( Edited 2021-11-23 05:01)

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
P#100442 2021-11-19 20:57

@dw817

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 :)

P#100457 2021-11-20 00:13

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 !

P#100496 2021-11-20 18:02

@dw817

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!

P#100516 2021-11-21 02:45

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
P#100517 2021-11-21 02:51 ( Edited 2021-11-21 02:54)

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.

P#100518 2021-11-21 03:17

> 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

P#100527 2021-11-21 05:38

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 ?

P#100552 2021-11-21 17:00

... 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)()
P#100570 2021-11-21 18:51 ( Edited 2021-11-21 21:02)

> 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

P#100609 2021-11-22 03:50 ( Edited 2021-11-22 06:01)

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
P#108523 2022-03-12 23:44 ( Edited 2022-03-12 23:45)

@Cerb043_4

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.

P#108525 2022-03-13 01:21

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-03-29 11:31:31 | 0.047s | Q:28