diff --git a/signal/signal.can b/signal/signal.can index 004160f..c732e32 100644 --- a/signal/signal.can +++ b/signal/signal.can @@ -99,7 +99,7 @@ let signal = { -- For example, every ubiquitousse module with a "update" function will bind it to the "update" signal in the registry; -- you can then call this signal on each game update to update every ubiquitousse module easily. -- Provided signals: - -- * update, should be called on every game update + -- * update(dt), should be called on every game update -- * draw, should be called on every game draw -- * for LÖVE, there are callbacks for every LÖVE callback function that need to be called on their corresponding LÖVE callback -- @impl mixed diff --git a/timer/backend/ctrulua.lua b/timer/backend/ctrulua.lua deleted file mode 100644 index 05143c1..0000000 --- a/timer/backend/ctrulua.lua +++ /dev/null @@ -1,6 +0,0 @@ -local timer = require((...):match("^(.-%.)backend").."timer") -local ctr = require("ctr") - -timer.get = ctr.time - -return timer \ No newline at end of file diff --git a/timer/backend/love.lua b/timer/backend/love.lua deleted file mode 100644 index ea57669..0000000 --- a/timer/backend/love.lua +++ /dev/null @@ -1,7 +0,0 @@ -local timer = require((...):match("^(.-%.)backend").."timer") - -timer.get = function() - return love.timer.getTime() * 1000 -end - -return timer \ No newline at end of file diff --git a/timer/init.lua b/timer/init.lua index 2b758c4..0b454dc 100644 --- a/timer/init.lua +++ b/timer/init.lua @@ -1,14 +1 @@ -local time - -local p = ... -if love then - time = require(p..".backend.love") -elseif package.loaded["ctr"] then - time = require(p..".backend.ctrulua") -elseif package.loaded["libretro"] then - error("NYI") -else - error("no backend for ubiquitousse.timer") -end - -return time +return require((...)..".timer") diff --git a/timer/timer.lua b/timer/timer.lua index a17ef44..10ea0a1 100644 --- a/timer/timer.lua +++ b/timer/timer.lua @@ -1,5 +1,4 @@ --- ubiquitousse.timer --- Depends on a backend. -- Optional dependencies: ubiquitousse.signal (to bind to update signal in signal.event) local loaded, signal = pcall(require, (...):match("^(.-)timer").."signal") if not loaded then signal = nil end @@ -29,15 +28,9 @@ end local registry_mt = { --- Update all the TimedFunctions calls. -- Should be called at every game update; called by ubiquitousse.update. - -- @tparam[opt=calculate here] number dt the delta-time (time spent since last time the function was called) (miliseconds) + -- @tparam number dt the delta-time (time spent since last time the function was called) (miliseconds) -- @impl ubiquitousse update = function(self, dt) - local currentTime = timer.get() - - if not dt then - dt = currentTime - self.lastTime - self.lastTime = currentTime - end self.dt = dt local done = {} -- functions done running @@ -47,36 +40,50 @@ local registry_mt = { if t and all(t.initWhen, true) then t.initWhen = {} local co = t.coroutine - t.after = t.after - dt - if t.forceStart or (t.after <= 0 and all(t.startWhen, true)) then + -- skip + dt = self.dt + if t.skip then dt = dt + t.skip end + -- start + if t.after then t.after = t.after - dt end + if t.forceStart or ((not t.after or t.after <= 0) and all(t.startWhen, true)) then + local startLag = 0 + if t.after then + startLag = -t.after + elseif t.skip then + startLag = t.skip + end + t.after, t.skip = nil, nil t.startWhen = {} d[func] = false -- niling here cause the next pair iteration to error table.insert(done, func) if not co then co = coroutine.create(func) t.coroutine = co - t.started = currentTime if t.times > 0 then t.times = t.times - 1 end - for _, f in ipairs(t.onStart) do f(t.object) end + for _, f in ipairs(t.onStart) do f(t.object, startLag) end end + -- update assert(coroutine.resume(co, function(delay) - t.after = delay or 0 + t.after = delay - startLag d[func] = t coroutine.yield() end, dt)) - for _, f in ipairs(t.onUpdate) do f(t.object) end + for _, f in ipairs(t.onUpdate) do f(t.object, startLag) end + if t.during then t.during = t.during - startLag - dt end + -- stopping / repeat if all(t.stopWhen, false) then t.forceStop = true end if t.forceStop or coroutine.status(co) == "dead" then if t.forceStop - or (t.during >= 0 and t.started + t.during < currentTime) + or (t.during and t.during <= 0) or (t.times == 0) or (not all(t.repeatWhile, true)) - or (t.every == -1 and t.times == -1 and t.during == -1 and #t.repeatWhile == 0) -- no repeat + or (t.every == nil and t.times == -1 and t.during == nil and #t.repeatWhile == 0) -- no repeat then - for _, f in ipairs(t.onEnd) do f(t.object) end + local endLag = t.during and -t.during or 0 + for _, f in ipairs(t.onEnd) do f(t.object, endLag) end else if t.times > 0 then t.times = t.times - 1 end - t.after = t.every + if t.every then t.after = t.every - startLag end t.coroutine = coroutine.create(func) d[func] = t end @@ -95,6 +102,7 @@ local registry_mt = { --- Schedule a function to run. -- The function will receive as first parameter the wait(time) function, which will pause the function execution for time miliseconds. -- As a second parameter, the function will receive the delta time (dt). + -- As a third parameter, the function will receive the lag time (difference between the time when the function was run and when it should have been run). -- @tparam[opt] function func the function to schedule -- @treturn TimedFunction the object -- @impl ubiquitousse @@ -107,12 +115,11 @@ local registry_mt = { self.delayed[func] = { object = nil, coroutine = nil, - started = 0, - after = -1, - every = -1, + after = nil, + every = nil, times = -1, - during = -1, + during = nil, initWhen = {}, startWhen = {}, @@ -121,6 +128,7 @@ local registry_mt = { forceStart = false, forceStop = false, + skip = nil, onStart = {}, onUpdate = {}, @@ -132,21 +140,25 @@ local registry_mt = { r = { --- Timed conditions --- --- Wait time milliseconds before running the function. + -- Specify no time to remove condition. after = function(_, time) t.after = time return r end, --- Run the function every time millisecond. + -- Specify no time to remove condition. every = function(_, time) t.every = time return r end, --- The function will not execute more than count times. + -- Specify no time to remove condition. times = function(_, count) - t.times = count + t.times = count or -1 return r end, --- The TimedFunction will be active for a time duration. + -- Specify no time to remove condition. during = function(_, time) t.during = time return r @@ -191,32 +203,40 @@ local registry_mt = { t.forceStop = true return r end, + --- Skip some amount of time. + skip = function(_, time) + t.skip = (t.skip or 0) + time + end, --- Callbacks functions --- - --- Will execute func(self) when the function execution start. + --- Will execute func(self, lag) when the function execution start. onStart = function(_, func) table.insert(t.onStart, func) return r end, - --- Will execute func(self) each frame the main function is run. + --- Will execute func(self, lag) each frame the main function is run. onUpdate = function(_, func) table.insert(t.onUpdate, func) return r end, - --- Will execute func(self) when the function execution end. + --- Will execute func(self, lag) when the function execution end. onEnd = function(_, func) table.insert(t.onEnd, func) return r end, --- Chaining --- - --- Creates another TimedFunction which will be initialized when the current one ends. + --- Creates another TimedFunction which will be initialized immediately when the current one ends. -- Returns the new TimedFunction. chain = function(_, func) local done = false - r:onEnd(function() done = true end) - return self:run(func) + local fn = self:run(func) :initWhen(function() return done end) + r:onEnd(function(self, lag) + done = true + fn:skip(lag) + end) + return fn end } t.object = r @@ -284,9 +304,13 @@ local registry_mt = { end local done = false - r:onEnd(function() done = true end) - return self:tween(duration_, tbl_, to_, method_) + local fn = self:tween(duration_, tbl_, to_, method_) :initWhen(function() return done end) + r:onEnd(function(self, lag) + done = true + fn:skip(lag) + end) + return fn end return r @@ -314,20 +338,11 @@ timer = { -- This table is for internal use and shouldn't be used from an external script. delayed = {}, - -- Used to calculate the deltatime - lastTime = timer.get(), - --- Time since last timer update (miliseconds). dt = 0 }, registry_mt) end, - --- Returns the number of miliseconds elapsed since some point in time. - -- This point is fixed but undetermined, so this function should only be used to calculate durations. - -- Should at least have millisecond-precision, but can be more precise if available. - -- @impl backend - get = function() end, - --- Time since last update (miliseconds). -- @impl ubiquitousse dt = 0, @@ -335,7 +350,6 @@ timer = { --- Global TimerRegistry. -- @impl ubiquitousse delayed = {}, - lastTime = 0, update = function(...) -- If ubiquitousse.signal is available, will be bound to the "update" signal in signal.event. return registry_mt.update(timer, ...) end,