New timer design.
authorAuke Kok <sofar@foo-projects.org>
Thu, 21 Jan 2016 09:07:38 +0000 (01:07 -0800)
committerShadowNinja <shadowninja@minetest.net>
Fri, 29 Jan 2016 06:04:51 +0000 (01:04 -0500)
I could honestly not make much sense of the timer implementation
that was here. Instead I've implemented the type of timer algorithm
that I've used before, and tested it instead.

The concept is extremely simple: all timers are put in an ordered
list. We check every server tick if any of the timers have
elapsed, and execute the function associated with this timer.

We know that many timers by themselves cause new timers to be
added to this list, so we iterate *backwards* over the timer
list. This means that new timers being added while timers are
being executed, can never be executed in the same function pass,
as they are always appended to the table *after* the end of
the table, which we will never reach in the current pass over
all the table elements.

We switch time keeping to minetest.get_us_time(). dtime is
likely unreliable and we have our own high-res timer that we
can fix if it is indeed broken. This removes the need to do
any sort of time keeping.

builtin/game/misc.lua
doc/lua_api.txt

index efd0f8dc74e2c5383174a4ca5846f311aa894511..bacadf18f71f88026d1cc45508624ff144442774 100644 (file)
@@ -4,74 +4,48 @@
 -- Misc. API functions
 --
 
-local timers = {}
-local mintime
-local function update_timers(delay)
-       mintime = false
-       local sub = 0
-       for index = 1, #timers do
-               index = index - sub
-               local timer = timers[index]
-               timer.time = timer.time - delay
-               if timer.time <= 0 then
-                       core.set_last_run_mod(timer.mod_origin)
-                       timer.func(unpack(timer.args or {}))
-                       table.remove(timers, index)
-                       sub = sub + 1
-               elseif mintime then
-                       mintime = math.min(mintime, timer.time)
-               else
-                       mintime = timer.time
-               end
-       end
-end
+local jobs = {}
+local time = 0.0
+local last = 0.0
 
-local timers_to_add
-local function add_timers()
-       for _, timer in ipairs(timers_to_add) do
-               table.insert(timers, timer)
+core.register_globalstep(function(dtime)
+       local new = core.get_us_time() / 1000000
+       if new > last then
+               time = time + (new - last)
+       else
+               -- Overflow, we may lose a little bit of time here but
+               -- only 1 tick max, potentially running timers slightly
+               -- too early.
+               time = time + new
        end
-       timers_to_add = false
-end
+       last = new
 
-local delay = 0
-core.register_globalstep(function(dtime)
-       if not mintime then
-               -- abort if no timers are running
+       if #jobs < 1 then
                return
        end
-       if timers_to_add then
-               add_timers()
-       end
-       delay = delay + dtime
-       if delay < mintime then
-               return
+
+       -- Iterate backwards so that we miss any new timers added by
+       -- a timer callback, and so that we don't skip the next timer
+       -- in the list if we remove one.
+       for i = #jobs, 1, -1 do
+               local job = jobs[i]
+               if time >= job.expire then
+                       core.set_last_run_mod(job.mod_origin)
+                       job.func(unpack(job.arg))
+                       table.remove(jobs, i)
+               end
        end
-       update_timers(delay)
-       delay = 0
 end)
 
-function core.after(time, func, ...)
+function core.after(after, func, ...)
        assert(tonumber(time) and type(func) == "function",
                        "Invalid core.after invocation")
-       if not mintime then
-               mintime = time
-               timers_to_add = {{
-                       time   = time+delay,
-                       func   = func,
-                       args   = {...},
-                       mod_origin = core.get_last_run_mod(),
-               }}
-               return
-       end
-       mintime = math.min(mintime, time)
-       timers_to_add = timers_to_add or {}
-       timers_to_add[#timers_to_add+1] = {
-               time   = time+delay,
-               func   = func,
-               args   = {...},
-               mod_origin = core.get_last_run_mod(),
-       }
+       table.insert(jobs, {
+               func = func,
+               expire = time + after,
+               arg = {...},
+               mod_origin = core.get_last_run_mod()
+       })
 end
 
 function core.check_player_privs(player_or_name, ...)
index 7255852e0dfa57dae7593674706b074a1c81a247..b6bc957c16a76574a8c904c924850091f4a21d5f 100644 (file)
@@ -2242,7 +2242,7 @@ These functions return the leftover itemstack.
 
 ### Timing
 * `minetest.after(time, func, ...)`
-    * Call the function `func` after `time` seconds
+    * Call the function `func` after `time` seconds, may be fractional
     * Optional: Variable number of arguments that are passed to `func`
 
 ### Server