# A dt based timer for callbacks

**URL:** <https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139>\
**Category:** The Defoldmine\
**Created:** [February 24, 2018, 12:38am UTC](https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139 "2018-02-24T00:38:52Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![tacklemcclean](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/tacklemcclean/32/7634_2.png) [@tacklemcclean](https://forum.defold.com/u/tacklemcclean)\
**Post date:** [February 24, 2018, 12:38am UTC](https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139/1 "2018-02-24T00:38:52Z")

</div>

[britzl’s timer library](https://www.defold.com/community/projects/76569/) is great for a os time based timer.

Inspired by it, and needing a similar solution for handling events in my game but based on passed in game time rather than os time, I wrote a simple Lua module for it.

`timer.lua`:

```lua
local M = {}
local callbacks = {}
local currentId = 0

local function buildCallback(func, seconds, ...)
	currentId = currentId + 1
	return {
		instance = __dm_script_instance__ ,
		id = currentId,
		callback = func,
		time = seconds,
		timeLeft = seconds,
		repeating = false,
		paused = false,
		args = { n = select("#", ...), ... }
	}
end

function M.once(func, seconds, ...)
	local callback = buildCallback(func, seconds, ...)
	table.insert(callbacks, callback)
	return currentId
end

function M.repeating(func, seconds, ...)
	local callback = buildCallback(func, seconds, ...)
	callback.repeating = true
	table.insert(callbacks, callback)
	return currentId
end

function M.cancel(id)
	for i = 1, #callbacks do
		if callbacks[i].id == id then
			table.remove(callbacks, i)
			return true
		end
	end
	return false
end

function M.pause(id)
	for i = 1, #callbacks do
		if callbacks[i].id == id then
			callbacks[i].paused = true
			return true
		end
	end
	return false
end

function M.unpause(id)
	for i = 1, #callbacks do
		if callbacks[i].id == id then
			callbacks[i].paused = false
			return true
		end
	end
	return false
end

function M.cancel_all()
	callbacks = {}
end

function M.update(dt)
	for i = #callbacks, 1, -1 do
		local callback = callbacks[i]
		if callback.paused == false then
			callback.timeLeft = callback.timeLeft - dt
			if callback.timeLeft <= 0 then
				if callback.repeating == true then
					callback.timeLeft = callback.time
				else
					table.remove(callbacks, i)
				end
				if pcall( function() local test = callback.instance.property end ) then
					local currentInstance = __dm_script_instance__
					__dm_script_instance__ = callback.instance
					callback.callback(unpack(callback.args, 1, callback.args.n))
					__dm_script_instance__ = currentInstance
				end
			end
		end
	end
end

return M

```

How to use it? First, make sure you set up a game object dedicated to updating the timer, like so:

```lua
local timer = require "path/to/your/modules/timer"

function update(self, dt)
	timer.update(dt)
end

```

And finally, how to actually add your timed functions:

```lua
-- simple call, supply your callback and seconds until trigger
timer.once(function()
	print("Hello world!")
end, 0.5)

-- third/rest parameters turn into arguments for the callback
timer.once(function(name)
	print("Oh hai " .. name .. "!")
end, 0.8, "Mark")

-- example of using more than one argument for the callback
timer.once(function(who, data)
	print("It's " .. data.state .. ", I did not " .. data.action .. " " .. who)
end, 1, "her", { action = "hit", state = "not true"} )

-- and id is returned, can be used to cancel
local timerId = timer.once(function(name)
	print("This will never trigger since we cancel it")
end, 2)
timer.cancel(timerId)

-- how to have a repeating callback instead
local repeatingId = timer.repeating(function(state)
	state.count = state.count + 1
	print("Repeating, count: " .. state.count)
end, 1, {count = 0} )

-- better cancel that repeater if we don't want it to run forever!
timer.once(function(id)		
	if timer.cancel(repeatingId) then
		print("Cancelled the repeater")
	end
end, 4, repeatingId)

```

Result:

```lua
DEBUG:SCRIPT: Hello world!
DEBUG:SCRIPT: Oh hai Mark!
DEBUG:SCRIPT: Repeating, count: 1
DEBUG:SCRIPT: It's not true, I did not hit her
DEBUG:SCRIPT: Repeating, count: 2
DEBUG:SCRIPT: Repeating, count: 3
DEBUG:SCRIPT: Cancelled the repeater

```

* * *

I’m not an expert on Lua, so if anyone more experienced want to vet the code please do not hesitate to call on improvements!

I haven’t gotten it working properly with functions referring to `self` yet, perhaps someone can give some input on that?  
For example, if I have a callback that calls go.get\_id(), it returns `hash: [/timer]`. So it’s in the context of the object calling update.  
Is there way to call a function in the context of a specific game object?

---

<div class="post-metadata">

**Author:** ![dapetcu21](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/dapetcu21/32/25120_2.png) [@dapetcu21](https://forum.defold.com/u/dapetcu21)\
**Post date:** [February 24, 2018, 7:59am UTC](https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139/2 "2018-02-24T07:59:38Z")

</div>

> [@tacklemcclean](#):
>
> Is there way to call a function in the context of a specific game object?

Nope. Not officially (unless you use native extensions). There is a hack to do it, though. There is this global variable named ` __dm_script_instance__ ` which you can save when registering the callback and then temporarily set while calling it.

See discussion here:

> [@Run function in context of another script](https://forum.defold.com/t/run-function-in-context-of-another-script/9862):
>
> Hi! So, I have a timer module in my project. The problem I’m facing is the fact that the timer callback gets called in the context of the script instance that called the timer’s update() function, not in the context of the script instance that registered the timer. I think on the native side, you can do dmScript::getInstance() and dmScript::setInstance(). Would setting \_\_dm\_script\_instance\_\_ on the global object from the Lua side, then setting it back have the same effect or is there potential…

---

<div class="post-metadata">

**Author:** ![tacklemcclean](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/tacklemcclean/32/7634_2.png) [@tacklemcclean](https://forum.defold.com/u/tacklemcclean)\
**Post date:** [February 24, 2018, 12:29pm UTC](https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139/3 "2018-02-24T12:29:32Z")

</div>

Great tip, thank you!

I’ve modified my timer.lua module to employ this method, and it works great now with my own scenarios (self-exploding missiles happening after x seconds, now behaving exactly the same on both desktop and mobile).

I was a bit worried that my timers would keep a reference to the instances and bugs would ensue.

Consider this scenario for example:

- Missile is launched, sets a self desctruct timer to 5 seconds.
- Collides with player after 4 seconds.
- In the `final`-method I call timer.cancel to remove the self destruct timer.
- Works fine

However, if I skip the third step (cancelling), I was expecting bad things to happen. But instead it works out great, in that my callbacks table automatically has the timer removed. I’m not sure how/why, but I’m guessing the engine looks up references somehow and deems it suitable for removal.

---

<div class="post-metadata">

**Author:** ![britzl](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/britzl/32/23_2.png) [@britzl](https://forum.defold.com/u/britzl)\
**Post date:** [February 25, 2018, 7:03am UTC](https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139/4 "2018-02-25T07:03:52Z")

</div>

How can it be automatically removed from the callback table? Sounds strange to me.

---

<div class="post-metadata">

**Author:** ![tacklemcclean](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/tacklemcclean/32/7634_2.png) [@tacklemcclean](https://forum.defold.com/u/tacklemcclean)\
**Post date:** [February 25, 2018, 9:57am UTC](https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139/5 "2018-02-25T09:57:24Z")

</div>

Yep you’re right. Just produced an isolated example project for this and it makes the engine crash instead. Not sure where in my game I accidentally take care of it.

So now the question is, is it possible to check on an instance if it has been killed or not. I realize there might not be support for this since it’s not supposed to be used and stored.

Such as `dmScript::IsInstanceValid`

---

<div class="post-metadata">

**Author:** ![dapetcu21](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/dapetcu21/32/25120_2.png) [@dapetcu21](https://forum.defold.com/u/dapetcu21)\
**Post date:** [February 25, 2018, 10:34am UTC](https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139/6 "2018-02-25T10:34:53Z")

</div>

It’s good practice to clean up after yourself in `final()` anyway, so why not do that? Invalidate timers corresponding to the current script in final.

---

<div class="post-metadata">

**Author:** ![tacklemcclean](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/tacklemcclean/32/7634_2.png) [@tacklemcclean](https://forum.defold.com/u/tacklemcclean)\
**Post date:** [February 25, 2018, 10:41am UTC](https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139/7 "2018-02-25T10:41:35Z")

</div>

Yeah that’s fine I guess, just pushing the boundary to see if there’s a way to take care of those cases where you accidentally **do** forget 🙂

---

<div class="post-metadata">

**Author:** ![tacklemcclean](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/tacklemcclean/32/7634_2.png) [@tacklemcclean](https://forum.defold.com/u/tacklemcclean)\
**Post date:** [February 25, 2018, 5:22pm UTC](https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139/8 "2018-02-25T17:22:35Z")

</div>

I managed to solve it in a sneaky way.

On my saved instance, I can do:  
`pcall( function() local test = v.instance.property end )`  
This returns true if the instance is still valid, and false if it has been deleted. Instead of directly trying to use the saved instance which would result in a crash if it is no longer valid.

I have updated my timer.lua code above to reflect this, and this version thus supports the irresponsible developer that does not clean up any timers after themselves in the `final` function.

---

<div class="post-metadata">

**Author:** ![tacklemcclean](https://forum-defold.b-cdn.net/user_avatar/forum.defold.com/tacklemcclean/32/7634_2.png) [@tacklemcclean](https://forum.defold.com/u/tacklemcclean)\
**Post date:** [February 28, 2018, 12:01am UTC](https://forum.defold.com/t/a-dt-based-timer-for-callbacks/17139/9 "2018-02-28T00:01:30Z")

</div>

Updated the code to use regular for-loops instead of ipairs. Supposedly this is more performant, and if this is to be called on each update I might as well take that into account, should anyone use this and have hundreds of timers at the same time.
