first commit
This commit is contained in:
15
lib/knife/base.lua
Normal file
15
lib/knife/base.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
return {
|
||||
extend = function (self, subtype)
|
||||
subtype = subtype or {}
|
||||
local meta = { __index = subtype }
|
||||
return setmetatable(subtype, {
|
||||
__index = self,
|
||||
__call = function (self, ...)
|
||||
local instance = setmetatable({}, meta)
|
||||
return instance, instance:constructor(...)
|
||||
end
|
||||
})
|
||||
end,
|
||||
constructor = function () end,
|
||||
}
|
||||
|
||||
78
lib/knife/behavior.lua
Normal file
78
lib/knife/behavior.lua
Normal file
@@ -0,0 +1,78 @@
|
||||
-- behavior.lua -- a state manager
|
||||
|
||||
-- internal/external api
|
||||
|
||||
local function getCurrentFrame (behavior)
|
||||
return behavior.states[behavior.state][behavior.index]
|
||||
end
|
||||
|
||||
local function advanceFrame (behavior)
|
||||
local nextState = behavior.frame.after
|
||||
local nextIndex = behavior.index + 1
|
||||
local maxIndex = #behavior.states[behavior.state]
|
||||
|
||||
if nextState then
|
||||
behavior.state = nextState
|
||||
nextIndex = 1
|
||||
elseif nextIndex > maxIndex then
|
||||
nextIndex = 1
|
||||
end
|
||||
|
||||
behavior.index = nextIndex
|
||||
behavior.frame = behavior:getCurrentFrame()
|
||||
end
|
||||
|
||||
local function performAction (behavior)
|
||||
local act = behavior.frame.action
|
||||
|
||||
if act then
|
||||
act(behavior, behavior.subject)
|
||||
end
|
||||
end
|
||||
|
||||
-- external api
|
||||
|
||||
local function update (behavior, dt)
|
||||
behavior.elapsed = behavior.elapsed + dt
|
||||
|
||||
while behavior.elapsed >= behavior.frame.duration do
|
||||
behavior.elapsed = behavior.elapsed - behavior.frame.duration
|
||||
behavior:advanceFrame()
|
||||
behavior:performAction()
|
||||
end
|
||||
|
||||
return behavior
|
||||
end
|
||||
|
||||
local function setState (behavior, state, index)
|
||||
behavior.state = state
|
||||
behavior.index = index or 1
|
||||
behavior.elapsed = 0
|
||||
behavior.frame = behavior:getCurrentFrame()
|
||||
behavior:performAction()
|
||||
return behavior
|
||||
end
|
||||
|
||||
-- behavior factory
|
||||
|
||||
return function (states, subject)
|
||||
local behavior = {
|
||||
states = states,
|
||||
subject = subject,
|
||||
elapsed = 0,
|
||||
state = 'default',
|
||||
index = 1,
|
||||
-- internal api
|
||||
getCurrentFrame = getCurrentFrame,
|
||||
advanceFrame = advanceFrame,
|
||||
performAction = performAction,
|
||||
-- external api
|
||||
update = update,
|
||||
setState = setState
|
||||
}
|
||||
|
||||
behavior.frame = behavior:getCurrentFrame()
|
||||
behavior:performAction()
|
||||
|
||||
return behavior
|
||||
end
|
||||
27
lib/knife/bind.lua
Normal file
27
lib/knife/bind.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
local loadstring = _G.loadstring or _G.load
|
||||
local tconcat = table.concat
|
||||
|
||||
local helperCache = {}
|
||||
|
||||
local function buildHelper (argCount)
|
||||
if helperCache[argCount] then
|
||||
return helperCache[argCount]
|
||||
end
|
||||
local argList1 = { 'f' }
|
||||
local argList2 = {}
|
||||
for index = 1, argCount do
|
||||
argList1[index + 1] = 'a' .. index
|
||||
argList2[index] = 'a' .. index
|
||||
end
|
||||
argList2[argCount + 1] = '...'
|
||||
local source = 'return function(' .. tconcat(argList1, ', ') ..
|
||||
') return function(...) return f(' .. tconcat(argList2, ', ') ..
|
||||
') end end'
|
||||
local helper = loadstring(source)()
|
||||
helperCache[argCount] = helper
|
||||
return helper
|
||||
end
|
||||
|
||||
return function (func, ...)
|
||||
return buildHelper(select('#', ...))(func, ...)
|
||||
end
|
||||
31
lib/knife/chain.lua
Normal file
31
lib/knife/chain.lua
Normal file
@@ -0,0 +1,31 @@
|
||||
local function Invoker (links, index)
|
||||
return function (...)
|
||||
local link = links[index]
|
||||
if not link then
|
||||
return
|
||||
end
|
||||
local continue = Invoker(links, index + 1)
|
||||
local returned = link(continue, ...)
|
||||
if returned then
|
||||
returned(function (_, ...) continue(...) end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return function (...)
|
||||
local links = { ... }
|
||||
|
||||
local function chain (...)
|
||||
if not (...) then
|
||||
return Invoker(links, 1)(select(2, ...))
|
||||
end
|
||||
local offset = #links
|
||||
for index = 1, select('#', ...) do
|
||||
links[offset + index] = select(index, ...)
|
||||
end
|
||||
return chain
|
||||
end
|
||||
|
||||
return chain
|
||||
end
|
||||
|
||||
54
lib/knife/convoke.lua
Normal file
54
lib/knife/convoke.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
return function (routine)
|
||||
local routines = { routine }
|
||||
local routineIndex = 1
|
||||
local isFinished = false
|
||||
|
||||
local function execute ()
|
||||
local continueCount = 0
|
||||
local run
|
||||
|
||||
local function continue ()
|
||||
continueCount = continueCount + 1
|
||||
return function (...)
|
||||
continueCount = continueCount - 1
|
||||
if continueCount == 0 then
|
||||
return run(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function wait (...)
|
||||
return coroutine.yield(...)
|
||||
end
|
||||
|
||||
local r = coroutine.create(function ()
|
||||
isFinished = false
|
||||
while routineIndex <= #routines do
|
||||
routines[routineIndex](continue, wait)
|
||||
continueCount = 0
|
||||
routineIndex = routineIndex + 1
|
||||
end
|
||||
isFinished = true
|
||||
end)
|
||||
|
||||
run = function (...)
|
||||
return coroutine.resume(r, ...)
|
||||
end
|
||||
|
||||
run()
|
||||
end
|
||||
|
||||
local function appendOrExecute (routine)
|
||||
if routine then
|
||||
routines[#routines + 1] = routine
|
||||
if isFinished then
|
||||
execute()
|
||||
end
|
||||
return appendOrExecute
|
||||
else
|
||||
execute()
|
||||
end
|
||||
end
|
||||
|
||||
return appendOrExecute
|
||||
end
|
||||
111
lib/knife/event.lua
Normal file
111
lib/knife/event.lua
Normal file
@@ -0,0 +1,111 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
-- Event module
|
||||
local Event = {}
|
||||
|
||||
-- Event handler registry
|
||||
Event.handlers = {}
|
||||
|
||||
-- Remove an event handler from the registry
|
||||
local function remove (self)
|
||||
if not self.isRegistered then
|
||||
return self
|
||||
end
|
||||
if self.prevHandler then
|
||||
self.prevHandler.nextHandler = self.nextHandler
|
||||
end
|
||||
if self.nextHandler then
|
||||
self.nextHandler.prevHandler = self.prevHandler
|
||||
end
|
||||
if Event.handlers[self.name] == self then
|
||||
Event.handlers[self.name] = self.nextHandler
|
||||
end
|
||||
self.prevHandler = nil
|
||||
self.nextHandler = nil
|
||||
self.isRegistered = false
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Insert an event handler into the registry
|
||||
local function register (self)
|
||||
if self.isRegistered then
|
||||
return self
|
||||
end
|
||||
self.nextHandler = Event.handlers[self.name]
|
||||
if self.nextHandler then
|
||||
self.nextHandler.prevHandler = self
|
||||
end
|
||||
Event.handlers[self.name] = self
|
||||
self.isRegistered = true
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Create an event handler
|
||||
local function Handler (name, callback)
|
||||
return {
|
||||
name = name,
|
||||
callback = callback,
|
||||
isRegistered = false,
|
||||
remove = remove,
|
||||
register = register
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Create and register a new event handler
|
||||
function Event.on (name, callback)
|
||||
return register(Handler(name, callback))
|
||||
end
|
||||
|
||||
-- Dispatch an event
|
||||
function Event.dispatch (name, ...)
|
||||
local handler = Event.handlers[name]
|
||||
|
||||
while handler do
|
||||
if handler.callback(...) == false then
|
||||
return handler
|
||||
end
|
||||
handler = handler.nextHandler
|
||||
end
|
||||
end
|
||||
|
||||
local function isCallable (value)
|
||||
return type(value) == 'function' or
|
||||
getmetatable(value) and getmetatable(value).__call
|
||||
end
|
||||
|
||||
-- Inject a dispatcher into a table.
|
||||
local function hookDispatcher (t, key)
|
||||
local original = t[key]
|
||||
|
||||
if isCallable(original) then
|
||||
t[key] = function (...)
|
||||
original(...)
|
||||
return Event.dispatch(key, ...)
|
||||
end
|
||||
else
|
||||
t[key] = function (...)
|
||||
return Event.dispatch(key, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Inject dispatchers into a table. Examples:
|
||||
-- Event.hook(love.handlers)
|
||||
-- Event.hook(love, { 'load', 'update', 'draw' })
|
||||
function Event.hook (t, keys)
|
||||
if keys then
|
||||
for _, key in ipairs(keys) do
|
||||
hookDispatcher(t, key)
|
||||
end
|
||||
else
|
||||
for key in pairs(t) do
|
||||
hookDispatcher(t, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Event
|
||||
2
lib/knife/gun.lua
Normal file
2
lib/knife/gun.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
-- GUN (Give Up Now)
|
||||
os.exit()
|
||||
77
lib/knife/memoize.lua
Normal file
77
lib/knife/memoize.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
local loadstring = _G.loadstring or _G.load
|
||||
local weakKeys = { __mode = 'k' }
|
||||
local cache = setmetatable({}, weakKeys)
|
||||
local resultsKey = {}
|
||||
local nilKey = {}
|
||||
|
||||
local function getMetaCall (callable)
|
||||
local meta = getmetatable(callable)
|
||||
return meta and meta.__call
|
||||
end
|
||||
|
||||
local tupleConstructorCache = {}
|
||||
|
||||
local function buildTupleConstructor (n)
|
||||
if tupleConstructorCache[n] then
|
||||
return tupleConstructorCache[n]
|
||||
end
|
||||
local t = {}
|
||||
for i = 1, n do
|
||||
t[i] = "a" .. i
|
||||
end
|
||||
local args = table.concat(t, ',')
|
||||
local ctor = loadstring('return function(' .. args ..
|
||||
') return function() return ' .. args .. ' end end')()
|
||||
tupleConstructorCache[n] = ctor
|
||||
return ctor
|
||||
end
|
||||
|
||||
local function tuple (...)
|
||||
return buildTupleConstructor(select('#', ...))(...)
|
||||
end
|
||||
|
||||
return function (callable)
|
||||
local metaCall = getMetaCall(callable)
|
||||
|
||||
if type(callable) ~= 'function' and not metaCall then
|
||||
error 'Attempted to memoize a non-callable value.'
|
||||
end
|
||||
|
||||
cache[callable] = setmetatable({}, weakKeys)
|
||||
|
||||
local function run (...)
|
||||
local node = cache[callable]
|
||||
local argc = select('#', ...)
|
||||
for i = 1, argc do
|
||||
local key = select(i, ...)
|
||||
if key == nil then
|
||||
key = nilKey
|
||||
end
|
||||
if not node[key] then
|
||||
node[key] = setmetatable({}, weakKeys)
|
||||
end
|
||||
node = node[key]
|
||||
end
|
||||
|
||||
if not node[resultsKey] then
|
||||
node[resultsKey] = tuple(callable(...))
|
||||
end
|
||||
|
||||
return node[resultsKey]()
|
||||
end
|
||||
|
||||
if metaCall then
|
||||
return function (...)
|
||||
local call = getMetaCall(callable)
|
||||
|
||||
if call ~= metaCall then
|
||||
cache[callable] = setmetatable({}, weakKeys)
|
||||
metaCall = call
|
||||
end
|
||||
|
||||
return run(...)
|
||||
end, cache, resultsKey, nilKey
|
||||
end
|
||||
|
||||
return run, cache, resultsKey, nilKey
|
||||
end
|
||||
78
lib/knife/serialize.lua
Normal file
78
lib/knife/serialize.lua
Normal file
@@ -0,0 +1,78 @@
|
||||
local tinsert, tconcat, infinity = table.insert, table.concat, math.huge
|
||||
|
||||
return function (value)
|
||||
local intro, outro, ready, known = {}, {}, {}, {}
|
||||
local knownCount = 0
|
||||
local writer = {}
|
||||
|
||||
-- get writer delegate for this value's type
|
||||
local function getWriter (value)
|
||||
return writer[type(value)]
|
||||
end
|
||||
|
||||
-- check if a value has a representation yet
|
||||
local function isReady (value)
|
||||
return type(value) ~= 'table' or ready[value]
|
||||
end
|
||||
|
||||
-- serialize tables
|
||||
function writer.table (value)
|
||||
if known[value] then
|
||||
return known[value]
|
||||
end
|
||||
|
||||
knownCount = knownCount + 1
|
||||
local variable = ('v%i'):format(knownCount)
|
||||
known[value] = variable
|
||||
|
||||
local parts = {}
|
||||
for k, v in pairs(value) do
|
||||
local writeKey, writeValue = getWriter(k), getWriter(v)
|
||||
if writeKey and writeValue then
|
||||
local key, value = writeKey(k), writeValue(v)
|
||||
if isReady(k) and isReady(v) then
|
||||
tinsert(parts, ('[%s]=%s'):format(key, value))
|
||||
else
|
||||
tinsert(outro, ('%s[%s]=%s'):format(variable, key, value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local fields = tconcat(parts, ',')
|
||||
tinsert(intro, ('local %s={%s}'):format(variable, fields))
|
||||
ready[value] = true
|
||||
|
||||
return variable
|
||||
end
|
||||
|
||||
-- preserve sign bit on NaN, since Lua prints "nan" or "-nan"
|
||||
local function writeNan (n)
|
||||
return tostring(n) == tostring(0/0) and '0/0' or '-(0/0)'
|
||||
end
|
||||
|
||||
-- serialize numbers
|
||||
function writer.number (value)
|
||||
return value == infinity and '1/0'
|
||||
or value == -infinity and '-1/0'
|
||||
or value ~= value and writeNan(value)
|
||||
or ('%.17G'):format(value)
|
||||
end
|
||||
|
||||
-- serialize strings
|
||||
function writer.string (value)
|
||||
return ('%q'):format(value)
|
||||
end
|
||||
|
||||
-- serialize booleans
|
||||
writer.boolean = tostring
|
||||
|
||||
-- concatenate array, joined by and terminated with line break
|
||||
local function lines (t)
|
||||
return #t == 0 and '' or tconcat(t, '\n') .. '\n'
|
||||
end
|
||||
|
||||
-- generate serialized result
|
||||
local write = getWriter(value)
|
||||
local result = write and write(value) or 'nil'
|
||||
return lines(intro) .. lines(outro) .. 'return ' .. result
|
||||
end
|
||||
69
lib/knife/system.lua
Normal file
69
lib/knife/system.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
local loadstring = _G.loadstring or _G.load
|
||||
local tconcat = table.concat
|
||||
local type = type
|
||||
|
||||
local function hasSigil (sigil, value)
|
||||
return type(value) == 'string' and sigil:byte() == value:byte()
|
||||
end
|
||||
|
||||
return function (aspects, process)
|
||||
local args = {}
|
||||
local cond = {}
|
||||
local results = {}
|
||||
local localIndex = 0
|
||||
local choicePattern = '([^|]+)'
|
||||
|
||||
local function suppress (aspect, condition)
|
||||
cond[#cond + 1] = 'if nil'
|
||||
for option in aspect:gmatch(choicePattern) do
|
||||
cond[#cond + 1] = condition:format(option)
|
||||
end
|
||||
cond[#cond + 1] = 'then return end'
|
||||
end
|
||||
|
||||
local function supply (aspect, isOptional, isReturned)
|
||||
localIndex = localIndex + 1
|
||||
cond[#cond + 1] = ('local l%d = nil'):format(localIndex)
|
||||
for option in aspect:gmatch(choicePattern) do
|
||||
cond[#cond + 1] = ('or _entity[%q]'):format(option)
|
||||
end
|
||||
if not isOptional then
|
||||
cond[#cond + 1] = ('if not l%d then return end'):format(localIndex)
|
||||
end
|
||||
if isReturned then
|
||||
results[#results + 1] = ('_entity[%q]'):format(aspect)
|
||||
end
|
||||
args[#args + 1] = ('l%d'):format(localIndex)
|
||||
end
|
||||
|
||||
for index = 1, #aspects do
|
||||
local aspect = aspects[index]
|
||||
if hasSigil('_', aspect) then
|
||||
args[#args + 1] = aspect
|
||||
elseif hasSigil('!', aspect) or hasSigil('~', aspect) then
|
||||
suppress(aspect:sub(2), 'or _entity[%q]')
|
||||
elseif hasSigil('-', aspect) then
|
||||
suppress(aspect:sub(2), 'or not _entity[%q]')
|
||||
elseif hasSigil('?', aspect) then
|
||||
supply(aspect:sub(2), true)
|
||||
elseif hasSigil('=', aspect) then
|
||||
supply(aspect:sub(2), false, true)
|
||||
else
|
||||
supply(aspect, false)
|
||||
end
|
||||
end
|
||||
|
||||
local source = ([[
|
||||
local _aspects, _process = ...
|
||||
return function (_entity, ...)
|
||||
%s
|
||||
%s _process(%s ...)
|
||||
return true
|
||||
end]]):format(
|
||||
tconcat(cond, ' '),
|
||||
results[1] and (tconcat(results, ',') .. ' = ') or '',
|
||||
args[1] and (tconcat(args, ', ') .. ', ') or '')
|
||||
|
||||
return loadstring(source)(aspects, process)
|
||||
end
|
||||
|
||||
128
lib/knife/test.lua
Normal file
128
lib/knife/test.lua
Normal file
@@ -0,0 +1,128 @@
|
||||
local test, testAssert, testError
|
||||
|
||||
-- Create a node representing a test section
|
||||
local function createNode (parent, description, process)
|
||||
return setmetatable({
|
||||
parent = parent,
|
||||
description = description,
|
||||
process = process,
|
||||
nodes = {},
|
||||
activeNodeIndex = 1,
|
||||
currentNodeIndex = 0,
|
||||
assert = testAssert,
|
||||
error = testError,
|
||||
}, { __call = test })
|
||||
end
|
||||
|
||||
-- Run a node
|
||||
local function runNode (node)
|
||||
node.currentNodeIndex = 0
|
||||
return node:process()
|
||||
end
|
||||
|
||||
-- Get the root node for a given node
|
||||
local function getRootNode (node)
|
||||
local parent = node.parent
|
||||
return parent and getRootNode(parent) or node
|
||||
end
|
||||
|
||||
-- Update the active child node of the given node
|
||||
local function updateActiveNode (node, description, process)
|
||||
local activeNodeIndex = node.activeNodeIndex
|
||||
local nodes = node.nodes
|
||||
local activeNode = nodes[activeNodeIndex]
|
||||
|
||||
if not activeNode then
|
||||
activeNode = createNode(node, description, process)
|
||||
nodes[activeNodeIndex] = activeNode
|
||||
else
|
||||
activeNode.process = process
|
||||
end
|
||||
|
||||
getRootNode(node).lastActiveLeaf = activeNode
|
||||
|
||||
return activeNode
|
||||
end
|
||||
|
||||
-- Run the active child node of the given node
|
||||
local function runActiveNode (node, description, process)
|
||||
local activeNode = updateActiveNode(node, description, process)
|
||||
return runNode(activeNode)
|
||||
end
|
||||
|
||||
-- Get ancestors of a node, including the node
|
||||
local function getAncestors (node)
|
||||
local ancestors = { node }
|
||||
for ancestor in function () return node.parent end do
|
||||
ancestors[#ancestors + 1] = ancestor
|
||||
node = ancestor
|
||||
end
|
||||
return ancestors
|
||||
end
|
||||
|
||||
-- Print a message describing one execution path in the test scenario
|
||||
local function printScenario (node)
|
||||
local ancestors = getAncestors(node)
|
||||
for i = #ancestors, 1, -1 do
|
||||
io.stderr:write(ancestors[i].description or '')
|
||||
io.stderr:write('\n')
|
||||
end
|
||||
end
|
||||
|
||||
-- Print a message and stop the test scenario when an assertion fails
|
||||
local function failAssert (node, description, message)
|
||||
io.stderr:write(message or '')
|
||||
io.stderr:write('\n\n')
|
||||
printScenario(node)
|
||||
io.stderr:write(description or '')
|
||||
io.stderr:write('\n\n')
|
||||
error(message or '', 2)
|
||||
end
|
||||
|
||||
-- Create a branch node for a test scenario
|
||||
test = function (node, description, process)
|
||||
node.currentNodeIndex = node.currentNodeIndex + 1
|
||||
if node.currentNodeIndex == node.activeNodeIndex then
|
||||
return runActiveNode(node, description, process)
|
||||
end
|
||||
end
|
||||
|
||||
-- Test an assertion
|
||||
testAssert = function (self, value, description)
|
||||
if not value then
|
||||
return failAssert(self, description, 'Test failed: assertion failed')
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- Expect function f to fail
|
||||
testError = function (self, f, description)
|
||||
if pcall(f) then
|
||||
return failAssert(self, description, 'Test failed: expected error')
|
||||
end
|
||||
end
|
||||
|
||||
-- Create the root node for a test scenario
|
||||
local function T (description, process)
|
||||
local root = createNode(nil, description, process)
|
||||
|
||||
runNode(root)
|
||||
while root.activeNodeIndex <= #root.nodes do
|
||||
local lastActiveBranch = root.lastActiveLeaf.parent
|
||||
lastActiveBranch.activeNodeIndex = lastActiveBranch.activeNodeIndex + 1
|
||||
runNode(root)
|
||||
end
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
-- Run any other files passed from CLI.
|
||||
if arg and arg[0] and arg[0]:gmatch('test.lua') then
|
||||
_G.T = T
|
||||
for i = 1, #arg do
|
||||
dofile(arg[i])
|
||||
end
|
||||
_G.T = nil
|
||||
end
|
||||
|
||||
return T
|
||||
246
lib/knife/timer.lua
Normal file
246
lib/knife/timer.lua
Normal file
@@ -0,0 +1,246 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
local Timer = {}
|
||||
|
||||
-- group management
|
||||
|
||||
local function detach (group, item)
|
||||
local index = item.index
|
||||
|
||||
group[index] = group[#group]
|
||||
group[index].index = index
|
||||
group[#group] = nil
|
||||
item.groupField = nil
|
||||
end
|
||||
|
||||
local function attach (group, item)
|
||||
if item.groupField then
|
||||
detach (item.groupField, item)
|
||||
end
|
||||
|
||||
local index = #group + 1
|
||||
|
||||
item.index = index
|
||||
group[index] = item
|
||||
item.groupField = group
|
||||
item.lastGroup = group
|
||||
end
|
||||
|
||||
-- instance update methods
|
||||
|
||||
local function updateContinuous (self, dt)
|
||||
local cutoff = self.cutoff
|
||||
local elapsed = self.elapsed + dt
|
||||
|
||||
if self:callback(dt) == false or elapsed >= cutoff then
|
||||
if self.finishField then
|
||||
self:finishField(elapsed - cutoff)
|
||||
end
|
||||
self:remove()
|
||||
end
|
||||
|
||||
self.elapsed = elapsed
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local function updateIntermittent (self, dt)
|
||||
local duration = self.delay or self.interval
|
||||
local elapsed = self.elapsed + dt
|
||||
|
||||
while elapsed >= duration do
|
||||
elapsed = elapsed - duration
|
||||
if self.limitField then
|
||||
self.limitField = self.limitField - 1
|
||||
end
|
||||
if self:callback(elapsed) == false
|
||||
or self.delay or self.limitField == 0 then
|
||||
if self.finishField then
|
||||
self:finishField(elapsed)
|
||||
end
|
||||
self:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self.elapsed = elapsed
|
||||
end
|
||||
|
||||
local function updateTween (self, dt)
|
||||
local elapsed = self.elapsed + dt
|
||||
local plan = self.plan
|
||||
local duration = self.duration
|
||||
|
||||
self.elapsed = elapsed
|
||||
|
||||
if elapsed >= duration then
|
||||
for index = 1, #plan do
|
||||
local task = plan[index]
|
||||
|
||||
task.target[task.key] = task.final
|
||||
end
|
||||
if self.finishField then
|
||||
self:finishField(elapsed - duration)
|
||||
end
|
||||
self:remove()
|
||||
return
|
||||
end
|
||||
|
||||
local ease = self.easeField
|
||||
|
||||
for index = 1, #plan do
|
||||
local task = plan[index]
|
||||
local target, key = task.target, task.key
|
||||
local initial, change = task.initial, task.change
|
||||
|
||||
target[key] = ease(elapsed, initial, change, duration)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- shared instance methods
|
||||
|
||||
local defaultGroup = {}
|
||||
|
||||
local function group (self, group)
|
||||
if not group then
|
||||
group = defaultGroup
|
||||
end
|
||||
|
||||
attach(group, self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function remove (self)
|
||||
if self.groupField then
|
||||
detach(self.groupField, self)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function register (self)
|
||||
attach(self.lastGroup, self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function limit (self, limitField)
|
||||
self.limitField = limitField
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function finish (self, finishField)
|
||||
self.finishField = finishField
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function ease (self, easeField)
|
||||
self.easeField = easeField
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- tweening helper functions
|
||||
|
||||
local function planTween (definition)
|
||||
local plan = {}
|
||||
|
||||
for target, values in pairs(definition) do
|
||||
for key, final in pairs(values) do
|
||||
local initial = target[key]
|
||||
|
||||
plan[#plan + 1] = {
|
||||
target = target,
|
||||
key = key,
|
||||
initial = initial,
|
||||
final = final,
|
||||
change = final - initial,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return plan
|
||||
end
|
||||
|
||||
local function easeLinear (elapsed, initial, change, duration)
|
||||
return change * elapsed / duration + initial
|
||||
end
|
||||
|
||||
-- instance initializer
|
||||
|
||||
local function initialize (timer)
|
||||
timer.elapsed = 0
|
||||
timer.group = group
|
||||
timer.remove = remove
|
||||
timer.register = register
|
||||
|
||||
attach(defaultGroup, timer)
|
||||
|
||||
return timer
|
||||
end
|
||||
|
||||
-- static api
|
||||
|
||||
function Timer.after (delay, callback)
|
||||
return initialize {
|
||||
delay = delay,
|
||||
callback = callback,
|
||||
update = updateIntermittent,
|
||||
}
|
||||
end
|
||||
|
||||
function Timer.every (interval, callback)
|
||||
return initialize {
|
||||
interval = interval,
|
||||
callback = callback,
|
||||
update = updateIntermittent,
|
||||
limit = limit,
|
||||
finish = finish,
|
||||
}
|
||||
end
|
||||
|
||||
function Timer.prior (cutoff, callback)
|
||||
return initialize {
|
||||
cutoff = cutoff,
|
||||
callback = callback,
|
||||
update = updateContinuous,
|
||||
finish = finish,
|
||||
}
|
||||
end
|
||||
|
||||
function Timer.tween (duration, definition)
|
||||
return initialize {
|
||||
duration = duration,
|
||||
plan = planTween(definition),
|
||||
update = updateTween,
|
||||
easeField = easeLinear,
|
||||
ease = ease,
|
||||
finish = finish,
|
||||
}
|
||||
end
|
||||
|
||||
function Timer.update (dt, group)
|
||||
if not group then
|
||||
group = defaultGroup
|
||||
end
|
||||
for index = #group, 1, -1 do
|
||||
group[index]:update(dt)
|
||||
end
|
||||
end
|
||||
|
||||
function Timer.clear (group)
|
||||
if not group then
|
||||
group = defaultGroup
|
||||
end
|
||||
for i = 1, #group do
|
||||
group[i] = nil
|
||||
end
|
||||
end
|
||||
|
||||
Timer.defaultGroup = defaultGroup
|
||||
|
||||
return Timer
|
||||
Reference in New Issue
Block a user