1
0
Fork 0
mirror of https://github.com/Reuh/ubiquitousse.git synced 2025-10-27 17:19:31 +00:00

Rename time to timer

This commit is contained in:
Étienne Fildadut 2019-12-25 16:15:40 +01:00
parent b5324faace
commit 82bc7268e6
9 changed files with 37 additions and 37 deletions

View file

@ -0,0 +1,6 @@
local timer = require((...):match("^(.-%.)backend").."timer")
local ctr = require("ctr")
timer.get = ctr.time
return timer

7
timer/backend/love.lua Normal file
View file

@ -0,0 +1,7 @@
local timer = require((...):match("^(.-%.)backend").."timer")
timer.get = function()
return love.timer.getTime() * 1000
end
return timer

435
timer/easing.lua Normal file
View file

@ -0,0 +1,435 @@
--
-- Adapted from
-- Tweener's easing functions (Penner's Easing Equations)
-- and http://code.google.com/p/tweener/ (jstweener javascript version)
--
--[[
Disclaimer for Robert Penner's Easing Equations license:
TERMS OF USE - EASING EQUATIONS
Open source under the BSD License.
Copyright © 2001 Robert Penner
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
]]
-- For all easing functions:
-- t = elapsed time
-- b = begin
-- c = change == ending - beginning
-- d = duration (total time)
local pow = math.pow
local sin = math.sin
local cos = math.cos
local pi = math.pi
local sqrt = math.sqrt
local abs = math.abs
local asin = math.asin
local function linear(t, b, c, d)
return c * t / d + b
end
local function inQuad(t, b, c, d)
t = t / d
return c * pow(t, 2) + b
end
local function outQuad(t, b, c, d)
t = t / d
return -c * t * (t - 2) + b
end
local function inOutQuad(t, b, c, d)
t = t / d * 2
if t < 1 then
return c / 2 * pow(t, 2) + b
else
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
end
end
local function outInQuad(t, b, c, d)
if t < d / 2 then
return outQuad (t * 2, b, c / 2, d)
else
return inQuad((t * 2) - d, b + c / 2, c / 2, d)
end
end
local function inCubic (t, b, c, d)
t = t / d
return c * pow(t, 3) + b
end
local function outCubic(t, b, c, d)
t = t / d - 1
return c * (pow(t, 3) + 1) + b
end
local function inOutCubic(t, b, c, d)
t = t / d * 2
if t < 1 then
return c / 2 * t * t * t + b
else
t = t - 2
return c / 2 * (t * t * t + 2) + b
end
end
local function outInCubic(t, b, c, d)
if t < d / 2 then
return outCubic(t * 2, b, c / 2, d)
else
return inCubic((t * 2) - d, b + c / 2, c / 2, d)
end
end
local function inQuart(t, b, c, d)
t = t / d
return c * pow(t, 4) + b
end
local function outQuart(t, b, c, d)
t = t / d - 1
return -c * (pow(t, 4) - 1) + b
end
local function inOutQuart(t, b, c, d)
t = t / d * 2
if t < 1 then
return c / 2 * pow(t, 4) + b
else
t = t - 2
return -c / 2 * (pow(t, 4) - 2) + b
end
end
local function outInQuart(t, b, c, d)
if t < d / 2 then
return outQuart(t * 2, b, c / 2, d)
else
return inQuart((t * 2) - d, b + c / 2, c / 2, d)
end
end
local function inQuint(t, b, c, d)
t = t / d
return c * pow(t, 5) + b
end
local function outQuint(t, b, c, d)
t = t / d - 1
return c * (pow(t, 5) + 1) + b
end
local function inOutQuint(t, b, c, d)
t = t / d * 2
if t < 1 then
return c / 2 * pow(t, 5) + b
else
t = t - 2
return c / 2 * (pow(t, 5) + 2) + b
end
end
local function outInQuint(t, b, c, d)
if t < d / 2 then
return outQuint(t * 2, b, c / 2, d)
else
return inQuint((t * 2) - d, b + c / 2, c / 2, d)
end
end
local function inSine(t, b, c, d)
return -c * cos(t / d * (pi / 2)) + c + b
end
local function outSine(t, b, c, d)
return c * sin(t / d * (pi / 2)) + b
end
local function inOutSine(t, b, c, d)
return -c / 2 * (cos(pi * t / d) - 1) + b
end
local function outInSine(t, b, c, d)
if t < d / 2 then
return outSine(t * 2, b, c / 2, d)
else
return inSine((t * 2) -d, b + c / 2, c / 2, d)
end
end
local function inExpo(t, b, c, d)
if t == 0 then
return b
else
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
end
end
local function outExpo(t, b, c, d)
if t == d then
return b + c
else
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
end
end
local function inOutExpo(t, b, c, d)
if t == 0 then return b end
if t == d then return b + c end
t = t / d * 2
if t < 1 then
return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005
else
t = t - 1
return c / 2 * 1.0005 * (-pow(2, -10 * t) + 2) + b
end
end
local function outInExpo(t, b, c, d)
if t < d / 2 then
return outExpo(t * 2, b, c / 2, d)
else
return inExpo((t * 2) - d, b + c / 2, c / 2, d)
end
end
local function inCirc(t, b, c, d)
t = t / d
return(-c * (sqrt(1 - pow(t, 2)) - 1) + b)
end
local function outCirc(t, b, c, d)
t = t / d - 1
return(c * sqrt(1 - pow(t, 2)) + b)
end
local function inOutCirc(t, b, c, d)
t = t / d * 2
if t < 1 then
return -c / 2 * (sqrt(1 - t * t) - 1) + b
else
t = t - 2
return c / 2 * (sqrt(1 - t * t) + 1) + b
end
end
local function outInCirc(t, b, c, d)
if t < d / 2 then
return outCirc(t * 2, b, c / 2, d)
else
return inCirc((t * 2) - d, b + c / 2, c / 2, d)
end
end
local function inElastic(t, b, c, d, a, p)
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
if not p then p = d * 0.3 end
local s
if not a or a < abs(c) then
a = c
s = p / 4
else
s = p / (2 * pi) * asin(c/a)
end
t = t - 1
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
end
-- a: amplitud
-- p: period
local function outElastic(t, b, c, d, a, p)
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
if not p then p = d * 0.3 end
local s
if not a or a < abs(c) then
a = c
s = p / 4
else
s = p / (2 * pi) * asin(c/a)
end
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
end
-- p = period
-- a = amplitud
local function inOutElastic(t, b, c, d, a, p)
if t == 0 then return b end
t = t / d * 2
if t == 2 then return b + c end
if not p then p = d * (0.3 * 1.5) end
if not a then a = 0 end
local s
if not a or a < abs(c) then
a = c
s = p / 4
else
s = p / (2 * pi) * asin(c / a)
end
if t < 1 then
t = t - 1
return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
else
t = t - 1
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b
end
end
-- a: amplitud
-- p: period
local function outInElastic(t, b, c, d, a, p)
if t < d / 2 then
return outElastic(t * 2, b, c / 2, d, a, p)
else
return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
end
end
local function inBack(t, b, c, d, s)
if not s then s = 1.70158 end
t = t / d
return c * t * t * ((s + 1) * t - s) + b
end
local function outBack(t, b, c, d, s)
if not s then s = 1.70158 end
t = t / d - 1
return c * (t * t * ((s + 1) * t + s) + 1) + b
end
local function inOutBack(t, b, c, d, s)
if not s then s = 1.70158 end
s = s * 1.525
t = t / d * 2
if t < 1 then
return c / 2 * (t * t * ((s + 1) * t - s)) + b
else
t = t - 2
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
end
end
local function outInBack(t, b, c, d, s)
if t < d / 2 then
return outBack(t * 2, b, c / 2, d, s)
else
return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
end
end
local function outBounce(t, b, c, d)
t = t / d
if t < 1 / 2.75 then
return c * (7.5625 * t * t) + b
elseif t < 2 / 2.75 then
t = t - (1.5 / 2.75)
return c * (7.5625 * t * t + 0.75) + b
elseif t < 2.5 / 2.75 then
t = t - (2.25 / 2.75)
return c * (7.5625 * t * t + 0.9375) + b
else
t = t - (2.625 / 2.75)
return c * (7.5625 * t * t + 0.984375) + b
end
end
local function inBounce(t, b, c, d)
return c - outBounce(d - t, 0, c, d) + b
end
local function inOutBounce(t, b, c, d)
if t < d / 2 then
return inBounce(t * 2, 0, c, d) * 0.5 + b
else
return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
end
end
local function outInBounce(t, b, c, d)
if t < d / 2 then
return outBounce(t * 2, b, c / 2, d)
else
return inBounce((t * 2) - d, b + c / 2, c / 2, d)
end
end
return {
linear = linear,
inQuad = inQuad,
outQuad = outQuad,
inOutQuad = inOutQuad,
outInQuad = outInQuad,
inCubic = inCubic ,
outCubic = outCubic,
inOutCubic = inOutCubic,
outInCubic = outInCubic,
inQuart = inQuart,
outQuart = outQuart,
inOutQuart = inOutQuart,
outInQuart = outInQuart,
inQuint = inQuint,
outQuint = outQuint,
inOutQuint = inOutQuint,
outInQuint = outInQuint,
inSine = inSine,
outSine = outSine,
inOutSine = inOutSine,
outInSine = outInSine,
inExpo = inExpo,
outExpo = outExpo,
inOutExpo = inOutExpo,
outInExpo = outInExpo,
inCirc = inCirc,
outCirc = outCirc,
inOutCirc = inOutCirc,
outInCirc = outInCirc,
inElastic = inElastic,
outElastic = outElastic,
inOutElastic = inOutElastic,
outInElastic = outInElastic,
inBack = inBack,
outBack = outBack,
inOutBack = inOutBack,
outInBack = outInBack,
inBounce = inBounce,
outBounce = outBounce,
inOutBounce = inOutBounce,
outInBounce = outInBounce,
}

14
timer/init.lua Normal file
View file

@ -0,0 +1,14 @@
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

349
timer/timer.lua Normal file
View file

@ -0,0 +1,349 @@
--- ubiquitousse.timer
-- Depends on a backend.
local ease = require((...):match("^.-timer")..".easing")
local timer
--- Returns true if all the values in the list are true ; functions in the list will be called and the test will be performed on their return value.
-- Returns default if the list is empty.
local function all(list, default)
if #list == 0 then
return default
else
local r = true
for _,v in ipairs(list) do
if type(v) == "function" then
r = r and v()
else
r = r and v
end
end
return r
end
end
--- Registry methods.
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)
-- @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
local d = self.delayed
for func, t in pairs(d) do
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
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
end
assert(coroutine.resume(co, function(delay)
t.after = delay or 0
d[func] = t
coroutine.yield()
end, dt))
for _, f in ipairs(t.onUpdate) do f(t.object) end
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.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
then
for _, f in ipairs(t.onEnd) do f(t.object) end
else
if t.times > 0 then t.times = t.times - 1 end
t.after = t.every
t.coroutine = coroutine.create(func)
d[func] = t
end
end
end
end
end
for _, func in ipairs(done) do
if not d[func] then
d[func] = nil
end
end
end,
--- 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).
-- @tparam[opt] function func the function to schedule
-- @treturn TimedFunction the object
-- @impl ubiquitousse
run = function(self, func)
-- Creates empty function (the TimedFunction may be used for time measure or stuff like that which doesn't need a specific function)
func = func or function() end
-- Since delayed functions can end in any order, it doesn't really make sense to use a integer-keyed list.
-- Using the function as the key works and it's unique.
self.delayed[func] = {
object = nil,
coroutine = nil,
started = 0,
after = -1,
every = -1,
times = -1,
during = -1,
initWhen = {},
startWhen = {},
repeatWhile = {},
stopWhen = {},
forceStart = false,
forceStop = false,
onStart = {},
onUpdate = {},
onEnd = {}
}
local t = self.delayed[func] -- internal data
local r -- external interface
r = {
--- Timed conditions ---
--- Wait time milliseconds before running the function.
after = function(_, time)
t.after = time
return r
end,
--- Run the function every time millisecond.
every = function(_, time)
t.every = time
return r
end,
--- The function will not execute more than count times.
times = function(_, count)
t.times = count
return r
end,
--- The TimedFunction will be active for a time duration.
during = function(_, time)
t.during = time
return r
end,
--- Function conditions ---
--- Starts the function execution when func() returns true. Checked before the "after" condition,
-- meaning the "after" countdown starts when func() returns true.
-- If multiple init functions are added, init will trigger only when all of them returns true.
initWhen = function(_, func)
table.insert(t.initWhen, func)
return r
end,
--- Starts the function execution when func() returns true. Checked after the "after" condition.
-- If multiple start functions are added, start will trigger only when all of them returns true.
startWhen = function(_, func)
table.insert(t.startWhen, func)
return r
end,
--- When the functions ends, the execution won't stop and will repeat as long as func() returns true.
-- Will cancel timed repeat conditions if false but needs other timed repeat conditions to be true to create a new repeat.
-- If multiple repeat functions are added, a repeat will trigger only when all of them returns true.
repeatWhile = function(_, func)
table.insert(t.repeatWhile, func)
return r
end,
--- Stops the function execution when func() returns true. Checked before all timed conditions.
-- If multiple stop functions are added, stop will trigger only when all of them returns true.
stopWhen = function(_, func)
table.insert(t.stopWhen, func)
return r
end,
--- Conditions override ---
--- Force the function to start its execution.
start = function(_)
t.forceStart = true
return r
end,
--- Force the function to stop its execution.
stop = function(_)
t.forceStop = true
return r
end,
--- Callbacks functions ---
--- Will execute func(self) 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.
onUpdate = function(_, func)
table.insert(t.onUpdate, func)
return r
end,
--- Will execute func(self) 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.
-- Returns the new TimedFunction.
chain = function(_, func)
local done = false
r:onEnd(function() done = true end)
return self:run(func)
:initWhen(function() return done end)
end
}
t.object = r
return r
end,
--- Tween some numeric values.
-- @tparam number duration tween duration (miliseconds)
-- @tparam table tbl the table containing the values to tween
-- @tparam table to the new values
-- @tparam[opt="linear"] string/function method tweening method (string name or the actual function(time, start, change, duration))
-- @treturn TimedFunction the object. A duration is already defined, and the :chain methods takes the same arguments as tween (and creates a tween).
-- @impl ubiquitousse
tween = function(self, duration, tbl, to, method)
method = method or "linear"
method = type(method) == "string" and ease[method] or method
local time = 0 -- tweening time elapsed
local from = {} -- initial state
local function update(tbl_, from_, to_) -- apply the method to tbl_ recursively (doesn't handle cycles)
for k, v in pairs(to_) do
if type(v) == "table" then
update(tbl_[k], from_[k], to_[k])
else
if time < duration then
tbl_[k] = method(time, from_[k], v - from_[k], duration)
else
tbl_[k] = v
end
end
end
end
local r = self:run(function(wait, dt)
time = time + dt
update(tbl, from, to)
end):during(duration)
:onStart(function()
local function copy(stencil, source, dest) -- copy initial state recursively
for k, v in pairs(stencil) do
if type(v) == "table" then
if not dest[k] then dest[k] = {} end
copy(stencil[k], source[k], dest[k])
else
dest[k] = source[k]
end
end
end
copy(to, tbl, from)
end)
--- Creates another tween which will be initialized when the current one ends.
-- If tbl_ and/or method_ are not specified, the values from the current tween will be used.
-- Returns the new tween.
r.chain = function(_, duration_, tbl_, to_, method_)
if not method_ and to_ then
if type(to_) == "string" then
tbl_, to_, method_ = tbl, tbl_, to_
else
method_ = method
end
elseif not method_ and not to_ then
tbl_, to_, method_ = tbl, tbl_, method
end
local done = false
r:onEnd(function() done = true end)
return self:tween(duration_, tbl_, to_, method_)
:initWhen(function() return done end)
end
return r
end,
--- Cancels all the running TimedFunctions.
-- @impl ubiquitousse
clear = function(self)
self.delayed = {}
end
}
registry_mt.__index = registry_mt
--- Time related functions
timer = {
--- Creates and return a new TimerRegistry.
-- A TimerRegistry is a separate ubiquitousse.time instance: its TimedFunctions will be independant
-- from the one registered using ubiquitousse.time.run (the global TimerRegistry). If you use the scene
-- system, a scene-specific TimerRegistry is available at ubiquitousse.scene.current.time.
-- @impl ubiquitousse
new = function()
return setmetatable({
--- Used to store all the functions delayed with ubiquitousse.time.delay
-- The default implementation use the structure {<key: function> = <value: data table>, ...}
-- 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,
--- Global TimerRegistry.
-- @impl ubiquitousse
delayed = {},
lastTime = 0,
update = function(...)
return registry_mt.update(timer, ...)
end,
run = function(...)
return registry_mt.run(timer, ...)
end,
tween = function(...)
return registry_mt.tween(timer, ...)
end,
clear = function(...)
return registry_mt.clear(timer, ...)
end
}
return timer