Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safely removing running sequences #7

Merged
merged 6 commits into from
May 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 83 additions & 43 deletions sequence.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ local _previousUpdateTime = playdate.getCurrentTimeMilliseconds()
function sequence.new()
local new_sequence = {
-- runtime values
time = 0,
time = 0, -- in ms
cachedResultTimestamp = nil,
cachedResult = 0,
previousUpdateEasingIndex = nil,
isRunning = false,

duration = 0,
duration = 0, -- in ms
loopType = false,
easings = table.create(4, 0),
easingCount = 0,
Expand All @@ -52,27 +52,36 @@ function sequence.update( pacing )
pacing = pacing or 1

local currentTime = playdate.getCurrentTimeMilliseconds()
local deltaTime = ((currentTime-_previousUpdateTime) / 1000) * pacing
local deltaTime = math.floor((currentTime-_previousUpdateTime) * pacing)
_previousUpdateTime = currentTime

for index = #_runningSequences, 1, -1 do
local seq = _runningSequences[index]

seq:updateCallbacks( deltaTime )
if seq.isRunning == true then
local previousTime = seq.time

seq.time = seq.time + deltaTime
seq.cachedResultTimestamp = nil

seq:triggerCallbacks( previousTime, seq.time )

seq.time = seq.time + deltaTime
seq.cachedResultTimestamp = nil
if seq:isDone() then
seq.isRunning = false
end
end

if seq:isDone() then
if seq.isRunning == false then
table.remove(_runningSequences, index)
seq.isRunning = false
end
end
end

function sequence.print()
print("Sequences running:", #_runningSequences)
for index, seq in pairs(_runningSequences) do
local seqs = sequence.getRunningSequencesDbg()

print("Sequences running:", #seqs)
for index, seq in pairs(seqs) do
print(" Sequence", index, seq)
end
end
Expand All @@ -98,10 +107,10 @@ function sequence:from( from )

-- setup first empty easing at the beginning of the sequence
local newEasing = self:newEasing()
newEasing.timestamp = 0
newEasing.from = from
newEasing.to = from
newEasing.duration = 0
newEasing.timestamp = 0 -- in ms
newEasing.from = from -- in ms
newEasing.to = from -- in ms
newEasing.duration = 0 -- in ms
newEasing.fn = _easings.flat

return self
Expand All @@ -112,7 +121,7 @@ function sequence:to( to, duration, easingFunction, ... )

-- default parameters
to = to or 0
duration = duration or 0.3
duration = toMilliseconds(duration) or 300
easingFunction = easingFunction or _easings.inOutQuad
if type(easingFunction)=="string" then
easingFunction = _easings[easingFunction] or _easings.inOutQuad
Expand Down Expand Up @@ -189,7 +198,7 @@ end
function sequence:sleep( duration )
if not self then return end

duration = duration or 0.5
duration = toMilliseconds(duration) or 500
if duration==0 then
return self
end
Expand All @@ -213,7 +222,7 @@ end
function sequence:callback( fn, timeOffset )
if not self then return end

timeOffset = timeOffset or 0
timeOffset = toMilliseconds(timeOffset) or 0

local lastEasing = self.easings[self.easingCount]

Expand Down Expand Up @@ -304,7 +313,7 @@ function sequence:get( time )
return 0
end

time = time or self.time
time = toMilliseconds(time) or self.time

-- try to get cached result
if self.cachedResultTimestamp==time then
Expand All @@ -323,59 +332,75 @@ function sequence:get( time )
return result
end

function sequence:updateCallbacks( dt )
function sequence:triggerCallbacks( startTime, endTime )
if #self.callbacks==0 then
return
end
if endTime<=startTime then
return
end

local callTimeRange = function( clampedStart, clampedEnd)
local deltaTime = endTime - startTime

local triggerCallbacksClampedTimeRange = function( clampedStart, clampedEnd)
local isForward = true
if clampedStart>clampedEnd then
clampedStart, clampedEnd = clampedEnd, clampedStart
isForward = false
end

for index, cbObject in pairs(self.callbacks) do
if cbObject.timestamp>=clampedStart and cbObject.timestamp<=clampedEnd then
if type(cbObject.fn)=="function" then
cbObject.fn()
end
local doTrigger = false

if cbObject.timestamp>clampedStart and cbObject.timestamp<clampedEnd then
doTrigger = true
elseif isForward and cbObject.timestamp==clampedEnd then
doTrigger = true
elseif isForward==false and cbObject.timestamp==clampedStart then
doTrigger = true
elseif clampedStart==0 and cbObject.timestamp==0 and isForward then
doTrigger = true
end

if doTrigger and type(cbObject.fn)=="function" then
cbObject.fn()
end
end
end

-- most straightforward case: no loop
if not self.loopType then
local clampedTime = self:getClampedTime( self.time )
callTimeRange(clampedTime, clampedTime+dt)
local startTimeClamped = self:getClampedTime( startTime )
triggerCallbacksClampedTimeRange(startTimeClamped, startTimeClamped+deltaTime)
return
end

--
-- now we handle loops

-- probably rare case but we have to handle it
if dt>self.duration then
callTimeRange(0, self.duration)
if deltaTime>self.duration then
triggerCallbacksClampedTimeRange(0, self.duration)
end

local clampedTime, isForward = self:getClampedTime( self.time )
local endTime = clampedTime
local startTimeClamped, isForward = self:getClampedTime( startTime )
if isForward then
endTime = endTime + dt
endTime = startTimeClamped + deltaTime
else
endTime = endTime - dt
endTime = startTimeClamped - deltaTime
end

if endTime<0 then
callTimeRange(0, math.max(clampedTime, self:getClampedTime( endTime )))
triggerCallbacksClampedTimeRange(0, math.max(startTimeClamped, self:getClampedTime( endTime )))
elseif endTime>self.duration then
if self.loopType=="loop" then
callTimeRange(clampedTime, self.duration)
callTimeRange(0, self:getClampedTime( endTime ))
triggerCallbacksClampedTimeRange(startTimeClamped, self.duration)
triggerCallbacksClampedTimeRange(0, self:getClampedTime( endTime ))
else
callTimeRange(math.min(clampedTime, self:getClampedTime( endTime )), self.duration)
triggerCallbacksClampedTimeRange(math.min(startTimeClamped, self:getClampedTime( endTime )), self.duration)
end
else
callTimeRange(clampedTime, endTime)
triggerCallbacksClampedTimeRange(startTimeClamped, endTime)
end
end

Expand All @@ -388,7 +413,7 @@ function sequence:getClampedTime( time )

-- time is looped
if self.loopType=="loop" then
return time%self.duration, isForward
return math.floor(time%self.duration), isForward

-- time is mirrored / yoyo
elseif self.loopType=="mirror" then
Expand All @@ -398,7 +423,7 @@ function sequence:getClampedTime( time )
time = self.duration + self.duration - time
end

return time, isForward
return math.floor(time), isForward
end

-- time is normally clamped
Expand All @@ -415,10 +440,7 @@ function sequence:addRunning()
end

function sequence:removeRunning()
local indexInRunningTable = table.indexOfElement(_runningSequences, self)
if indexInRunningTable then
table.remove(_runningSequences, indexInRunningTable)
end
-- _runningSequences table will be updated in the next sequence.update()
self.isRunning = false
end

Expand Down Expand Up @@ -456,6 +478,18 @@ function sequence:isEmpty()
return self.easingCount==0
end

function sequence.getRunningSequencesDbg()
local result = {}

for index, seq in pairs(_runningSequences) do
if seq.isRunning then
table.insert( result, seq)
end
end

return result
end

-- new easing function
function _easings.flat(t, b, c, d)
return b
Expand All @@ -468,5 +502,11 @@ math.clamp = math.clamp or function(a, min, max)
return math.max(min, math.min(max, a))
end

-- convert a floating point second to a rounded int millisecond
function toMilliseconds(seconds)
if seconds==nil then return nil end
return math.floor(1000*seconds)
end