Custom timer. Make your version so we can share ways to do things.

Hi, just for fun and while studying metatables and OOP just made the first version of a custom timer, like the timer.performWithDelay with some differences.

What can this timer do?

It can autostart (like timer.performWithDelay);

It can have more than 1 interaction (like timer.performWithDelay)

It can call a function for each interaction (like timer.performWithDelay)

It can also be declared on the beginning of a scene and called later, and reused anytime you want. (less code to call when is called to run, faster start) - autostart=false. use variable.start() to start it.

By default when it ends it will delete itself. If you want to use it more than 1 time you need to use autoRemove=false.

It has pause, resume, remove, cancel and start methods.

it stores a special variable. It’s a counter while the timer is active. This can be used to use in counter. - variable.elapsedTime returns the time passed by the timer. it’s updated every frame.

This is my version:

local newTimer={} newTimer.prototype = {delay=1000, listener =function() end, interactions=1, autoRemove=true, autoStart=true, elapsedTime=0} newTimer.mt = {\_\_index = newTimer.prototype } function newTimer.new(oIn) local o=oIn or {} setmetatable(o, newTimer.mt) local counter\_interaction=1 local startTime local autoRemove=o.autoRemove local autoStart=o.autoStart local pausedTime=0 local function updateTime() local time = system.getTimer() if startTime and counter\_interaction and time+pausedTime \>= startTime+counter\_interaction\*o.delay then counter\_interaction=counter\_interaction+1 o.listener() if counter\_interaction\>o.interactions and o.interactions~=0 then if autoRemove then o.remove() else o.cancel() end end end if startTime then o.elapsedTime=(time+pausedTime-startTime) end end local function addListner() if updateTime then Runtime:addEventListener("enterFrame", updateTime) end end local function removeListner() if updateTime then Runtime:removeEventListener("enterFrame", updateTime) end end function o.start() counter\_interaction=1 autoRemove=o.autoRemove autoStart=o.autoStart pausedTime=0 o.resume() end function o.pause() pausedTime=system.getTimer() removeListner() end function o.resume() startTime=system.getTimer() addListner() end function o.cancel() removeListner() startTime=nil counter\_interaction=1 o.elapsedTime=0 pausedTime=0 end function o.remove() removeListner() for n,m in pairs (o) do o[m]=nil o[n]=nil end startTime=nil counter\_interaction=nil updateTime=nil addListner=nil removeListner=nil pausedTime=nil end if autoStart then o.start() end return o end

to use it, here are some examples:

local timer1 local timer2 local timer3 local delay=2000 local interactions=4 local counter=0 local function printTime() print (timer1.elapsedTime) end local function showTime() print("DO SOMETHING") counter=counter+1 if counter\>=interactions then Runtime:removeEventListener( "enterFrame", printTime ) end end timer1=newTimer.new({delay=delay, listener=showTime, interactions=interactions}) local function startTimer1() print ("pause timer1") timer1.pause() end local function resumeTimer1() print ("resume timer1") timer1.resume() end timer2=newTimer.new({delay=1500, listener=startTimer1}) timer3=newTimer.new({delay=5000, listener=resumeTimer1}) Runtime:addEventListener("enterFrame", printTime)

Can you make a version of a timer, so we can share different approaches? if you can not, can you at least point some other directions of the code? And justify your reasons please so we can all learn from it.

I used metatables to initiate variables, created some local functions that I didn’t want to be called outside the scope, created others that could. Just started to learn meta yesterday so bare with me. Never needed them, but I can see the potential using them.  I was thinking to pass all methods outside the .new() function but other variables will be exposed and I don’t want that, for the sake of the experience.

Regards,

Carlos.

Hi Carlos,

This is interesting reading.

Trying to grasp OOP myself, whereas metatables are just too alien at this point  :ph34r:

I think everyone is according that timer need an update :slight_smile:

My improvement of timer are not like yours. I don’t modify how work the timer but how they are managed.

I modify the way I use timer to answer to one problem. The way to pause many of them and resume or cancel them.

I only put all timer in an array.

Mytimer.[thetag]={all timer with the  tag}

Mytimer.noTag={all timer with no tag}

like this I can use them with tag as for transition. But if I remember well sometime I have some error because very often timer depend of gameobject and I resumed timer who do something on a nil object and I haven’t find a way to “kill” a timer when a gameObject is destroy.

@anaqim, metatables are not that hard (at least for what need it and what I know).

For example, in my code, I used metatables to initialize variables like this:

local newTimer={} newTimer.prototype = {delay=1000, listener =function() end, interactions=1, autoRemove=true, autoStart=true, elapsedTime=0} newTimer.mt = {\_\_index = newTimer.prototype }

This is like a constructor that I will use each time I create a new table, those tables will have the properties in newTimer.prototype.

for this to work you need need to connect the new tables to that constructor. For that I used:

local o=oIn or {} setmetatable(o, newTimer.mt)

What I’m saying here is that the new table will be connected to newTimer.mt and newTimer.mt is connected to prototype by __index. __index means that will call the prototype variables when the new table returns nil. My English is not that great to explain it better. but with an example I think you will understand. for example, if you create a table:

local a={} setmetatable (a, newTimer.mt) print (a.delay)

will return 1000. why? if you remove the setmetatable line, it will return nil. the setmetable line redirects the nil to the prototype table. this is only possible because you defined newTimer.mt = {__index = newTimer.prototype } –  the __index is that what makes possible that redirect. this line says that…when a table defined with setmetable…if it returns nil it will point to the newTimer.prototype.

The old way of doing things would be something like this:

function newTimer.new(paramsIn) local params=paramsIn or {} local delay\_var=params.time or 1000 local func\_var=params.func or function() end local interactions\_var=params.interactions or 1 local startTime=system.getTimer() local interaction=1

@remiduchalard, didn’t understand your response. My exercise is to share how each person creates an independent function, not what is used for. I could had used any other function as an example, just made this one because I read on another post about timers, and people saying that used custom timers, just resolved to do one myself, nothing more. I always used Corona timer without a problem, and for my needs, it always worked without a single problem. When I check Corona Widgets code, it’s always cleaner and better designed, than if I was the one doing it. I’m not comparing my code to Corona implementation. Just comparing styles between users, hoping we all learn better technics and with that better coding. I love a function well written, is like reading a good book. I always smile when I read a good block code.

Btw, using timers as transitions doesn’t look to me, good programming practice, but without seeing your actual code I can’t comment on that.

*edit* maybe I should had used a simple example…

A lot of my timers update a single object that has a chance of being destroyed underneath me, leading to tests inside like

if object.parent then -- still alive? (seems to be okay test unless in stuff like snapshot) -- I've had issues with the more common object.removeSelf if "widgets" -- has been required as that seems to monkey-patch the display object -- metatable, for purposes of that very method in fact...

The upshot being that auto-killing the timer if said object dies can be useful. I don’t know what a good interface would be, though.

I’ve found it useful to put coroutines in timers, e.g. see here and here from my Corona Geek materials.

Hi,

Unless I am mistaken, this seems more like a conversation about metatables and OOP. In which case either a better title to the thread might be needed, or a new thread created.

Moved: https://www.develephant.com/2018/06/a-simple-intro-to-metatables-and-classes/

-dev

@dev nice authoritative post!  

Here is the simple version… 

Without metatables all objects created from the same “class” will share the same memory address and thus the same values. 

local a = person:create() local b = person:create()

Now b is just a pointer to a.  Therefore a == b.

Using metatables ensures that each instance has a separate memory address and therefore different values.

local a = person:create() local b = person:create()

Now a ~= b and is a separate instance.

there are some great points being made here, but also some of it appears to be getting misread and/or miscommunicated (and/or maybe I myself am misreading and just nitpicking semantics - entirely possible!)

setmetatable all by itself won’t create a new instance, it just returns its first parameter - the table to which the metatable was applied.

if you call it like “setmetatable({}, _mt)”, it’s the “{}” that actually creates the new table, then setmetatable merely returns it.

if the table passed to setmetatable already exists, all you get back is its original reference, fe:

\_mt = { \_\_index=whatever} -- condensed form makes it appear setmetatable created something: local t = setmetatable({},\_mt) -- but longhand form reveals it's just the table constructor: local t1 = {} -- creation local t2 = setmetatable(t1,\_mt) -- meta-decoration of t1 returns t1 print(t1,t2) -- t1==t2

hth

@Dev, this post was not about OOP or metatables, but starting to look one.

I could not explain it better how metatables work and why and when to use them. Kudos for your explanation.

My point on this post was about sharing different approaches to the same problem. If this was a test, the task would be: “create your custom timer, show it here.”

Each person have its own way how to program, sharing different approaches would be nice to see and with hope, learn from it. I used metatable, just because I was learning it and it looked easier to initialize variables using it. The question was not about metatables…if you do a custom timer without using them that’s your way, it’s not wrong or right. In fact, I’ve another version without using oop or metatables.

*edit* - changed my answer to @starCrunch

@StarCrunch, my pause/resume code was just to prove that my timer actually works as it should be, it’s not optimized or the best implementation of it. I’m thinking of changing my pause function. newTimer.pause(X) where X is the time it will pause and it will resume after X time. You gave me some good ideas for improving my code :). Thanks.

@SGS, your explanation is not correct from what I learned about metatables. “Without metatables all objects created from the same “class” will share the same memory address and thus the same values”

This is not true (or I didn’t understand what you meant), and Dev explained it on his great “mini tutorial”. You can create a class without metatables and create different objects from it, I used for about 5 years without a problem.

@davebollinger, yeap your right.

Still, again, this post was not about OOP or metatables…read the title please or the text below the code or my @Dev response.

Regards,

Carlos.

@carloscosta

The thread is about share your custom timer.

But I think it need a previous question. What are timer problem? And then How to improve it?

In this thread there is a few answer :

  • A way to be define timer before use it

  • A way to reuse timer

  • A way to be manage globally

  • Link timer to game object

@remiduchalard, there is no timer problem. Just reinventing the wheel for learning propose, nothing more. Regarding improving. All code can be improved, even the one you think it can’t be :wink:

The “task” I’m asking is really simple. Think about the Corona timer.performWithDelay. Try to mimic it, and improve it if you like it for your needs, justify it. But at least do what corona timer does. Do your own way.

I tried to mimic the timer.performWithDelay with some “bonus features”.

I also changed how parameters work in the “timer” function because all my functions accept parameters the same way, with a table.

Corona sometimes uses tables (new version of display.newText) others not really (display.newImage, etc.). I just try to make them all the same way. So it’s easier for me to not forget how they work. I really have a bad memory.

I added the “elapsedTime” variable so it can be used as a counter without any additional code. So, if you need a counter…this timer can do that also.

Added also, “autoremove” parameter for simplifying the process when we need a simple timer used 1 time in a scene and you will not need it again. I don’t need this function since I put all my timers in a table and delete them when I leave the scene, but maybe someone else doesn’t do what I do and needs to delete them 1 by 1, this could be useful.

Added “autostart”. declared to false, you can simply declare all your timers at the beginning of you code…and just use timer1.start() when you need it. It will be a faster call than creating a new timer. In intensive fps apps this could be useful since it will not micro-lag when it’s called. You can also reuse the same timer, so this means faster access also to the timer since you don’t need to create another timer if you need one later.

If you need a better pause system or another feature I’m not thinking about just create it :slight_smile:

My goal is not the timer itself, but the way you create it. English is not my first language and I can’t explain it better than this.

If I was a teacher and asked 10 students to make a timer. If none copied, I would get 10 different codes and all doing the same(if I asked specifically what the timer should do). Since I know there are good programmers here, I was just asking to share the code of a common task so we could learn from each other.

Maybe you will use OOP, maybe you won’t. Since Lua is not a real OOP language if you use it, there are different ways of “simulating” it. Seeing your implementation itself could be a learning experience.

Maybe you will use metatables or not. That’s not important. What’s important is why you used it the way you did.

@carloscosta - My apologies for highjacking the thread. I apparently missed the point of creating a custom timer since that is not something I would generally do. Corona’s timer object works fine for all my needs.

-dev

@Dev, don’t apologie. Your post was very useful, even if it was not directly related to what I wanted in this thread. I also use Corona timer. Just created a version myself just to show my style in programming (well new style after some new reading), nothing more. I just wanted to learn the other programmer’s styles to compare and we all try to learn from it. I know my request requires some free time from other programmers to actually build a timer and show it here and maybe I’m asking too much. I don’t blame anyone if this thread will have 0 more custom timers…but hoping that will :wink:

@carloscota Thank you for your explanation. I hadn’t understand all that because it’s also not my first language.

I hadn’t understand it like you explain a few hours ago because timer work perfectly and seem very simple.

If I have to rewrite timer I won’t work like you did (and that’s why your thread is interesting)

To avoid a lot of enterframe event and optimize. I will code something like this(I will complete the code later):

local timerData={} local timerTime={} local timerInPause={} local actualTime=system.getTimer() local lastId=0 local function timerManager(event) local time = system.getTimer() while(time\>timerTime[1] and #timerTime\>=1) timerData.listener() table.remove(timerData,1) table.remove(timerTime,1) end end Runtime:addEventListener("enterFrame", timerManager) function newTimer(data) local absoluteTime=actualTime+data.delay lastId=lastId+1 data.id=lastId -- insert absoluteTime at the good place in the order list timerTime -- insert at the same place data in timerData -- example: -- timerTime={25565,25765,29565} -- timerData={data1,data2,data3} -- data={delay=25675,otherData} -- timerTime={25565,25675,25765,29565} -- timerData={data1,otherData,data2,data3} return data end function setPause(timerToPause) local timerId for key,value in ipairs(timerTime) do if(value==timerToPause.absoluteTime) then while(timerToPause.id!=timerData[key].id and key\<=lastId) do key=key+1 end timerId=key if(key==lastId and timerToPause.id!=timerData[key].id)then return false end break end end local timerDelay=timerToPause.absoluteTime-system.getTimer() timerToPause.delay=timerDelay timerToPause.pauseId=#timerInPause+1 table.insert(timerInPause,timerToPause) table.remove(timerData,timerId) &nbsp;&nbsp;&nbsp;&nbsp;table.remove(timerTime,timerId) end function resume(theTimer) local theTimerToResume=timerInPause[theTimer.pauseId] table.remove(timerInPause,theTimer.pauseId) newTimer(theTimerToResume) return theTimerToResume end ----------------------------------------------- -- to add a timer: MyTimer=newTimer({delay=100,listener=function()end}) setPause(MyTimer) local MyTimerBis=resume(MyTimer)

It’s made a long time ago I hadn’t code in lua but I still love it. Be tolerant

My code missing a lot of thing like manage timer of 0ms

This is not exactly what you are asking for, but similar enough I think it is worth sharing. I didn’t recreate a timer class/library, but rather “patched” the built-in library to better suit my needs: https://github.com/schroederapps/corona-timer2

Another approach for the timer project (not very different from the first version I created):

local newTimer={} newTimer.mt = {\_\_index = {delay=1000, listener =function() end, interactions=1, elapsedTime=0, startTime=0, counter\_interaction=1, pausedTime=0} } function newTimer.new(oIn) local o=oIn or {} setmetatable(o, newTimer.mt) o.updateTime=function() local startTime=o.startTime local time = system.getTimer() o.elapsedTime=(time+o.pausedTime-startTime) if startTime and o.counter\_interaction and time+o.pausedTime \>= startTime+o.counter\_interaction\*o.delay then o.counter\_interaction=o.counter\_interaction+1 o.listener(o) if o.counter\_interaction\>o.interactions and o.interactions~=0 then newTimer.cancel(o) -- newTimer.removeSelf(o) -- uncoment this lines if you want to autoremove the timer after it finish. end end end newTimer.resume(o) return o end function newTimer.removeListener(self) if self then Runtime:removeEventListener("enterFrame", self.updateTime) end end function newTimer.pause(self) if self then self.pausedTime=system.getTimer() newTimer.removeListener(self) end end function newTimer.cancel(self) newTimer.removeListener(self) end function newTimer.removeSelf(self) local s=self if s then if s.updateTime then newTimer.removeListener(s) end for n,m in pairs (s) do s[m]=nil s[n]=nil end s.listener=nil s=nil end return nil end function newTimer.resume(self) if self then self.startTime=system.getTimer() Runtime:addEventListener("enterFrame", self.updateTime) end end ------------------------------------------------------------------------------ code examples ----------------------------- local timer1 local delay=500 local interactions=5 local counter=1 local stop\_variable=3 local function showTime() print("Will stop in: "..interactions-counter) if counter==stop\_variable then print ("simulating changing scene without canceling the timer -- even if the timer is still running, removing the timer will not provide an error. in corona timer would give an error because timer was not paused before it was deleted.") print ("showing that timer is not empty before i call the removeSelf()") print ("\_\_\_\_\_\_\_") if timer1 then for n,m in pairs (timer1) do print (n,m) end end print ("\_\_\_\_\_\_\_") newTimer.removeSelf(timer1) print ("check if timer is empty after is removed from memory") print ("\_\_\_\_\_\_\_") if timer1 then for n,m in pairs (timer1) do print (n,m) end end timer=nil print ("\_\_\_\_\_\_\_") end counter=counter+1 end timer1=newTimer.new({delay=delay, listener=showTime, interactions=interactions})

@carloscosta  (Responding mostly to your pre-edit comments.) I brought these up as some notable “special case” timers, say for some extra variants like  newTimer.performWithDelayAndMonitorObject and newTimer.wrapCoroutineAndPerformWithDelay. The second one is only really useful if it can fire more than once, of course. I definitely wasn’t recommending any more esoteric internals for the “basic” timer.  :slight_smile:

Another special case I just remembered is letting the timer fire once immediately. Think clicking on a button that scrolls a window or increments a number, then does so again at intervals. ( newTimer.performOnceThenWithDelay?) Usually you can just first call the function you’ll pass as the body but it gets a little weird if you need to use  counter and friends. (Again, only makes sense with repeats.)

did you know you could fe:

-- aka "performOnceASAPThenEverySecondThereafter" timer.performWithDelay(1, function(event) event.source.\_delay = 1000 end, -1)

@davebollinger I’m vaguely aware of those internals, but a bit paranoid about tampering with anything not nailed down. :D (I do seem to remember  @Lerg  going to town with them on an episode of Corona Geek, though I couldn’t say which one.) But of course the topic is all about sussing these sorts of things out.  :slight_smile: