Pauseable timers and transitions with speed adjustment

Posted by Lerg, Posted on June 19, 2011, Last updated April 14, 2012

9 votes

After searching for pausable timers for corona I found only several implementations. Beebe used enterFrame listener to implement his own timers and transitions. Transition manager has memory leaks and lacks some functionality. So I decided to wrap around existing corona timers and transitions into my own implementation.

Remember to free the memory used by this module, I advice you you to call cleanTimersAndTransitions() function with an infinite timer (say one or two seconds delay).

UPD 2012-04-14: Version 1.2 released
UPD 2011-08-05: Version 1.1.2 released

Here is the code.

tnt.lua

-- Pauseable timers and transitions with speed adjustment
-- Author: Lerg
-- Release date: 2012-04-14
-- Version: 1.2
-- License: MIT
-- Web: http://developer.anscamobile.com/code/pausable-timers-and-transitions-speed-adjustment
--
-- USAGE:
--  Import this module with a desired name, for example:
--      tnt = require('tnt')
--  Then you create timers and transitions with the same logic as before:
--      timer1 = tnt:newTimer(1000, function () print('tick') end, 1, {name = 'Tick Timer', userData = 'User data', onEnd = function (event) print(event.name .. ' has completed') end})
--      trans1 = tnt:newTransition(object, {time = 1000, x = 480, name = 'Slide Transition', userData = 'User data', cycle = 10, backAndForth = true, onEnd = function (object, event) print(event.name .. ' has completed') end})
--  Name and userData arguments are optional. userData can be anything.
--  onEnd callback (or object listener) is fired once timer or transition has finished it's job completely, after all ticks or transition cycles.
--  With cycle param you can tell transition to loop. 0 - infinite times. You can also set backAndForth param.
--  Every instance has pause(), resume() and cancel() methods.
--  You can manage all timers and transitions with function like tnt:pauseAllTimers(), tnt:resumeAllTransitions() etc.
--  For speed adjustment first pause all timers and transitions, then modify tnt.speed to say 0.5, which means 2 times faster
--  and lastly resume all paused instances.
--
-- LIMITATIONS:
--  Doesn't work with delta transitions. Easings will start over after each pausing, it can be fixed, but I don't need it at the moment,
--  so didn't implemented. Fix would be to set up custom easings and pass elapsed time to each easing function.
--
-- CONTRIBUTORS:
--  CluelessIdeas (www.cluelessideas.com), TMApps (www.timemachineapps.com/blog)
--
-- CHANGELIST:
-- 1.2:
--  [Feature] Added cycling support for transitions. Both repeating and "back and forth" loops. Infinite and finite.
--  [Feature] Added onEnd listener support - callback to be called when timer or transition elapsed completely (repeative timers and transitions)
--  [Feature] Added table listeners support. Events are timer, timerEnd, transition, transitionEnd
--  [Feature] Added speed constants tnt.NORMAL, tnt.FAST and tnt.SLOW - feel free to use them or add your own.
-- 1.1.2:
--  [Bug] Transitions are wrongfully decided to be already ended.
--  [Feature] Added name and userData params for transtitions just like for timers.
--  [Feature] Added LuaDoc.
--  [Feature] Added default value for the count argument.
-- 1.1.1:
--  [Bug] Quick bugfix on remainingTime calculations.
-- 1.1:
--  [Bug] onComplete function is not getting called for transitions when pausing right before the event.
--  [Bug] Timers are not counting resting time from resuming till next pausing (before next tick).
--  [Feature] Added userData and name to actual timers instances, they are accessible through event callback function argument, like event.userData and event.name.
--  [Feature] Added cleanTimersAndTransitions() function which frees the memory on demand (you can call it every couple of seconds)
--
-- I can be found on the corona IRC channel.
 
-- Module table
local _M = {}
 
-- Game speed: 1 - normal, 0.5 - fast, 2 - slow
_M.speed = 1
 
_M.NORMAL = 1
_M.FAST = 0.5
_M.SLOW = 2
 
-- Every instance is hold here
local allTimers = {}
local allTransitions = {}
 
-- Cache
local tInsert = table.insert
local tRemove = table.remove
 
-- Pausable timers
-- @param duration number Transition duration.
-- @param callback function Function to be called on the each tick.
-- @param count number How many times to tick, 0 - unlimited. Default is 1.
-- @param params table Extra parameters. Optional.
--          name string The name for the timer. Available in the callback.
--          userData table Any user data. Available in the callback.
--          onEnd function A callback to call when timer is elapsed completely (count wise).
function _M:newTimer (duration, callback, count, params)
    -- Timer handler
    local tH = {}
    tH.speed = self.speed
    tH.start = system.getTimer()
    tH.duration = duration
    tH.callback = callback
    tH.count = count or 1
    tH.counter = 0
    tH.isInfinite = (count == 0)
    if params then
        tH.name = params.name
        tH.userData = params.userData
        tH.onEnd = params.onEnd
    end
    tH.shouldRemove = false
    tH.paused = false
    tH.intervalStartTime = tH.start
    tH.remainingTime = duration
 
    -- Internal function which fires up the actual callback function
    -- @param event Corona's timer event
    local function callbackWrapper (event)
        local tH_callback = tH.callback
        if tH_callback then
            event.userData = tH.userData
            event.name = tH.name
            if type(tH_callback) == 'function' then
                tH_callback(event)
            elseif type(tH_callback) == 'table' and type(tH_callback.timerEnd) == 'function' then
                tH_callback:timerEnd(event)
            end
            if not tH.isInfinite then
                tH.counter = tH.counter + 1
                if tH.counter >= tH.count then
                    tH:cancel()
                    local onEnd = tH.onEnd
                    if type(onEnd) == 'function' then
                        onEnd(event)
                    elseif type(onEnd) == 'table' and type(onEnd.timerEnd) == 'function' then
                        onEnd:timerEnd(event)
                    end
                end
            end
            tH.remainingTime = tH.duration
            tH.intervalStartTime = system.getTimer()
        else
            tH:cancel()
        end
    end
 
    tH.t = timer.performWithDelay(tH.duration * self.speed, callbackWrapper, tH.count)
 
    -- Cancels running timer and prepares for the resuming
    function tH:pause ()
        if self.t then
            timer.cancel(self.t)
        end
        if not self.paused then
            self.paused = true
            self.pausingTime = system.getTimer()
            self.remainingTime = self.remainingTime - (self.pausingTime - self.intervalStartTime)
            if self.remainingTime < 0 then
                self.remainingTime = 0
            end
        end
    end
 
    -- Initiates a fresh timer if paused
    function tH:resume ()
        if self.paused then
            self.paused = false
            if not self.isInfinite then
                -- Timer elapsed
                if self.counter >= self.count then
                    self:cancel()
                else
                    local function callbackDoubleWrapper (event)
                        callbackWrapper(event)
                        local ticksRemains = self.count - self.counter
                        if ticksRemains > 0 then
                            self.t = timer.performWithDelay(self.duration * _M.speed, callbackWrapper, ticksRemains)
                            self.speed = _M.speed
                        else
                            self:cancel()
                        end
                    end
                    self.intervalStartTime = system.getTimer()
                    self.t = timer.performWithDelay(self.remainingTime * _M.speed, callbackDoubleWrapper, 1)
                    self.speed = _M.speed
                end
            else
                local function callbackDoubleWrapper (event)
                    callbackWrapper(event)
                    self.t = timer.performWithDelay(self.duration * _M.speed, callbackWrapper, 0)
                end
                self.intervalStartTime = system.getTimer()
                self.t = timer.performWithDelay(self.remainingTime * _M.speed, callbackDoubleWrapper, 1)
                self.speed = _M.speed
            end
        end
    end
 
    -- Cancels actual timer instance and marks this handler to be removed
    function tH:cancel ()
        if self.t then
            timer.cancel(self.t)
        end
        self.shouldRemove = true
        self.callback = nil
    end
 
    tInsert(allTimers, tH)
    return tH
end
 
-- Pauses everything in the allTimers table
function _M:pauseAllTimers()
    local i
    local allTimersCount = #allTimers
    if allTimersCount > 0 then
        for i = allTimersCount, 1, -1 do
            local child = allTimers[i]
            if child.shouldRemove then
                tRemove(allTimers, i)
            else
                child:pause()
            end
        end
    end
end
 
-- Resumes everything in the allTimers table
function _M:resumeAllTimers()
    local i
    local allTimersCount = #allTimers
    if allTimersCount > 0 then
        for i = allTimersCount, 1, -1 do
            local child = allTimers[i]
            if child.shouldRemove then
                tRemove(allTimers, i)
            else
                child:resume()
            end
        end
    end
end
 
-- Cancels everything in the allTimers table
function _M:cancelAllTimers()
    local i
    local allTimersCount = #allTimers
    if allTimersCount > 0 then
        for i = allTimersCount, 1, -1 do
            local child = allTimers[i]
            child:cancel()
            tRemove(allTimers, i)
        end
    end
end
 
-- Pausable transitions
-- @param object table An object for which transition is applied.
-- @param params table Transition parameters.
--        name string The name for the transition. Available in the onComplete function. Optional.
--        userData table Any user data. Available in the onComplete function. Optional.
--        cycle number How many times to repeat transition. 0 - infinite. Optional.
--        backAndForth boolean Should it be back and forth cycling? Optional.
--        onEnd function A callback to call when transition is completed completely (count wise). Optional.
function _M:newTransition(object, params)
    -- Transition handler
    local tH = {name = params.name, userData = params.userData, originalTime = params.time, cycleCount = 0}
    local elapsed, elapsedCount, currentCountRemains
    local onComplete = params.onComplete
    local onEnd = params.onEnd
    local cycleTransition = params.cycle or 1
    local backAndForthCycling = params.backAndForth or false
    local initialValues = {}
    for k, v in pairs(params) do
        if k ~= 'onComplete' and k ~= 'onEnd' and k ~= 'time' and k ~= 'transition' and k ~= 'delta' and k ~= 'name' and k ~= 'userData' and k ~= 'cycle' and k ~= 'backAndForth' then
            initialValues[k] = object[k]
        end
    end
 
    -- This function is called for each completed transition to mark it's handler for removal
    local function callbackWrapper ()
        if type(onComplete) == 'function' then
            onComplete(object, {userData = tH.userData, name = tH.name})
        elseif type(onComplete) == 'table' and type(onComplete.transition) == 'function' then
            onComplete:transition(object, {userData = tH.userData, name = tH.name})
        end
        local doRepeat = false
        if cycleTransition > 0 then
            tH.cycleCount = tH.cycleCount + 1
            if tH.cycleCount >= cycleTransition then
                tH:cancel()
                if type(onEnd) == 'function' then
                    onEnd(object, {userData = tH.userData, name = tH.name})
                elseif type(onEnd) == 'table' and type(onEnd.transitionEnd) == 'function' then
                    onEnd:transitionEnd(object, {userData = tH.userData, name = tH.name})
                end
            else
                doRepeat = true
            end
        elseif cycleTransition == 0 then
            doRepeat = true
        end
        if doRepeat then
            transition.cancel(tH.t)
            tH.params.time = tH.originalTime
            tH.start = system.getTimer()
            tH.elapsed = nil
            for k, v in pairs(initialValues) do
                if not backAndForthCycling then
                    object[k] = v
                else
                    tH.params[k] = v
                    initialValues[k] = object[k]
                end
            end
            tH.t = transition.to(object, tH.params)
        end
    end
 
    tH.params = {}
    -- Make a shallow copy of the user's params so they are not messed up in the user's space
    for k, v in pairs(params) do tH.params[k] = v end
    tH.params.onComplete = callbackWrapper
    tH.params.time = tH.originalTime * self.speed
    tH.t = transition.to(object, tH.params)
    tH.start = system.getTimer()
    tH.speed = self.speed
 
    --  Stops current transiton and prepares for the resuming
    function tH:pause()
        if self.t then
            self.elapsed = (system.getTimer() - self.start) / self.speed
            transition.cancel(self.t)
        else
            self:cancel()
        end
    end
    -- Initiates a fresh transition if paused
    function tH:resume()
        if self.elapsed and not self.shouldRemove then
            -- Current speed
            local s = _M.speed
            self.params.time = (self.originalTime - self.elapsed) * s
            self.t = transition.to(object, self.params)
            self.start = system.getTimer() - self.elapsed * s
            self.speed = s
            self.elapsed = nil
        end
    end
    -- Cancels actual transition instance and marks this handler to be removed
    function tH:cancel()
        if self.t then
            transition.cancel(self.t)
        end
        self.shouldRemove = true
    end
 
    tInsert(allTransitions, tH)
    return tH
end
 
-- Pauses everything in the allTransitions table
function _M:pauseAllTransitions()
    local i
    local allTransitionsCount = #allTransitions
    if allTransitionsCount > 0 then
        for i = allTransitionsCount, 1, -1 do
            local child = allTransitions[i]
            if child.shouldRemove then
                tRemove(allTransitions, i)
            else
                child:pause()
            end
        end
    end
end
 
-- Resumes everything in the allTransitions table
function _M:resumeAllTransitions()
    local i
    local allTransitionsCount = #allTransitions
    if allTransitionsCount > 0 then
        for i = allTransitionsCount, 1, -1 do
            local child = allTransitions[i]
            if child.shouldRemove then
                tRemove(allTransitions, i)
            else
                child:resume()
            end
        end
    end
end
 
-- Cancels everything in the allTransitions table
function _M:cancelAllTransitions()
    local i
    local allTransitionsCount = #allTransitions
    if allTransitionsCount > 0 then
        for i = allTransitionsCount, 1, -1 do
            local child = allTransitions[i]
            child:cancel()
            tRemove(allTransitions, i)
        end
    end
end
 
-- Deletes unused instances (frees memory)
function _M:cleanTimersAndTransitions()
    local i
    local allTimersCount = #allTimers
    if allTimersCount > 0 then
        for i = allTimersCount, 1, -1 do
            local child = allTimers[i]
            if child.shouldRemove then
                tRemove(allTimers, i)
            end
        end
    end
    local allTransitionsCount = #allTransitions
    if allTransitionsCount > 0 then
        for i = allTransitionsCount, 1, -1 do
            local child = allTransitions[i]
            if child.shouldRemove then
                tRemove(allTransitions, i)
            end
        end
    end
end
 
return _M

I described the usage in the module itself and it's pretty straightforward. If you'll have any questions, feel free to ask.


Replies

teex84
User offline. Last seen 41 weeks 5 days ago. Offline
Joined: 12 Dec 2010

This is a must have. Thanks for sharing.

tmapps's picture
tmapps
User offline. Last seen 1 day 13 hours ago. Offline
Joined: 9 Feb 2011

Hi,
although I had implemented my own pausable timers, I started to used this due to it can also be used with transitions.

However, after making several days of testing, I realized that the timers code doesn't work properly when multiple pause-resume are executed.

There are two problems:
1. in resume method when a timer is not infinite and not(self.counter >= self.count), the attribute paused is not set to false until the doublecallback function is executed. So the pausingTime variable cannot set in pause method until the callback funcion will be executed.
2. the delay time used to resume a timer is based on pausingTime and the lastTickTime. But this relation doesn't fulfil in multiple pause-resume.

A better and more general approch to calculate the resting time for a timer can be found in

http://www.timemachineapps.com/blog/2011/07/timers-with-pause-in-corona-sdk/?lang=en

Anyway, I have fixed this code, how could I upload this code? Other improvements implemented are:
- callback invocation with event parameter.
- the inclusion of userdata in the timers in order to use them in the callback function. This is essential when you work with multiple timers working the same callback function.

Regards

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

(deleted)

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

Hello!

Right, I also had a feedback regarding this bug and regarding memory usage (tables are not cleared by themselves).

I fixed an issue with transitions (onComplete is not getting called when you pause right before it). And had no time to fix the timer thing (after resuming if you pause again it wouldn't count this elapsed time). Although I know the approach how to fix this, I would appreciate if you'll send me your modifications fixing those bugs and describing new features.

Can you contact me on the corona IRC channel? Or post a link to the pastebin. I'll review your code and make an update for the module.

Thanks

tmapps's picture
tmapps
User offline. Last seen 1 day 13 hours ago. Offline
Joined: 9 Feb 2011

Hello Lerg,
I have posted the code in pastebin, since I don't use the corona IRC channel. This is your code fixed, so you only have to replace it. I have used the approach of my old pausable timers, which are explained in my blog entry.

http://pastebin.com/NF2fgNHQ

Other improvements that I had in my pausable timers and that I've also included in you code are:
- Another parameter for user data. This information is included in the corona timer, and it is accessible through the event parameter of the callback function. As I told this is fundamental when you use several timers simultaneously and you need to identify what are the involved game elements.
- In order to use it, callback function has to be invoked with the event parameter. This is also added to your code.

On the other hand, we have also developed pausable transitions, if you want, we can also review your code in order to fix possible errors.

Finally, we be grateful to you for including a reference in this entry to our blog as authors of this fixing and the others new features.

Regards.
TMApps

http://www.timemachineapps.com/blog/

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

I am happy to announce that I've just released a new version of this module (1.1.1).

I got your code, tmapps, cleaned it up a bit and merged into the source. Thank you, indeed I had to setup remainingTime variable.

There are also other bugfixes and improvements.

tmapps's picture
tmapps
User offline. Last seen 1 day 13 hours ago. Offline
Joined: 9 Feb 2011

thanks! by the way, remainingTime was setup in the line 28 of my code: tH.remainingTime = tH.duration :-)

Regards

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

tmapps, I know, I was talking about your remainingTime var. duration instead of tH.duration is just a bit faster.

edgar86m
User offline. Last seen 5 weeks 46 min ago. Offline
Joined: 7 Jan 2011

Love this file, been using for a lot of the games I'm working on! Found a small issue...

When I pass a callback for a timer, you don't pass back the "event" param when you call the callback. So I wasn't able to get event.count when using your file. You can fix this by changing the following in the "_M:newTimer" function...

1
2
3
4
5
6
7
8
9
10
11
    local function callbackWrapper ()
        if tH.callback then
            tH.callback()
            if not tH.isInfinite then
                tH.counter = tH.counter + 1
            end
            tH.lastTickTime = system.getTimer()
        else
            tH.shouldRemove = true
        end
    end

to..

1
2
3
4
5
6
7
8
9
10
11
    local function callbackWrapper (event)
        if tH.callback then
            tH.callback(event)
            if not tH.isInfinite then
                tH.counter = tH.counter + 1
            end
            tH.lastTickTime = system.getTimer()
        else
            tH.shouldRemove = true
        end
    end

All I did was add an event param and pass that event to " tH.callback(event)"

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

Hi Edgar. Thanks.

Please look at the top of the page, there is already this implementation, I had an update.

liongera
User offline. Last seen 2 weeks 4 days ago. Offline
Joined: 4 May 2011

Hi Lerg, first of all thanks for the amazing work!!!

I'm sorry but i have a (maybe) dumb question, why at the end of the :new function you return tH instead of tH.t (the actual timer)?

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

@liongera
Because tH is a wrapper around the actual timer tH.t. This wrapper has :pause, :resume and :cancel methods, which actual timer hasn't.

liongera
User offline. Last seen 2 weeks 4 days ago. Offline
Joined: 4 May 2011

I see, thanks so much Lerg :)
I was trying to mantain the timer.performWithDelay interface, and i was struggling a little bit, but i got it working, thanks again!

edgar86m
User offline. Last seen 5 weeks 46 min ago. Offline
Joined: 7 Jan 2011

Found another small issue...

You need to change the following at the start of "function _M:newTimer"

1
tH.count = count

to...

1
tH.count = count == nil and 1 or count

The first line was giving me issues when I paused a timer in which I didn't pass a count (because count would be nil). Usually when you use Corona's timer.performWithDelay() if you do not pass in a count it assumes you only want to fire the function once.

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

Thanks edgar for pointing this out.
However I think the more correct line would be just
tH.count = count or 1
as usual default value assignment in Lua. We don't need to treat 'false' as a valid value.
Thanks, that will be in the next release, which is coming out soon.

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

Yay! A new version have been just released! 1.1.2 - even more stable transitions, added name and user data for transitions just like for timers.
Moving to perfection!
Thanks everyone for testing and advices.

edgar86m
User offline. Last seen 5 weeks 46 min ago. Offline
Joined: 7 Jan 2011

It's looking great! I'll be sure to use this updated version on my next game and let you know if any issues/bugs come up!

Thanks again for putting this together, really helping me out on a lot of games :).

One request I have, this came up during one of the last games I built.

I was making a game in which something swings back and fourth using your class, in which I wanted to increase the motion in which it swings back and fourth. You could do this by simply changing .speed, but I had an issue. I was also using your class to make other things move in the game which I didn't want the speed to change. The request I'm trying to make is... could you implement this class such that you are allowed to change the speed of individual timers/transitions? For the problem I just described, I just requires your file twice, and used a separate object for my main game transition/timers and another one for the swinging object. But it would be nice if it had built in functionality to just change the .speed of any timer/transition.

Let me know if this make sense, would be happy to clarify some more :)

BTW, love the updated comments in the code. Makes it much easier to read :)

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

Edgar, yeah, this is really what I haven't though about yet. And it's very easy to implement.

Other thing, would you like to have an ability to modify timers parameters on the fly?
Like
t = tnt:newTimer(1000, func, 0)
Then somewhen
t.duration = 5000
t.count = 1
And call for the changes to take the effect
t:update()

Maybe also some resetting function?
t:reset() - and it starts to tick from the beginning

Same for transitions. Actually you can modify transitions params even now
t = tnt:newTransition(object, {time = 1000, x =480})
t.params.x = 320
t:pause()
t:resume()

Is it interesting?

edgar86m
User offline. Last seen 5 weeks 46 min ago. Offline
Joined: 7 Jan 2011

That would be interesting! In my other game I actually wanted to modify my transition parameters on the fly, I didn't know you could do that! So I ended up making an ugly hack instead :(. Perhaps you could make an update params function to make it more obviously that you can change parameters on the fly?

Another thing that would be nice, this comes from the Tween library in Flash, is if you could have a "loop" parameter. If it's true the tween loops, if false it doesn't. This would save some coding on thing that just move back and fourth.

If you want to get more ideas on how to make your class more powerful and robust in regards to the transitions take a look at the green sock library...

http://www.greensock.com/get-started-tweening/

It's the most advance tween library I seen. It's written in AS3 (which is very similar to Lua). If you take a look at the link you can even see examples right there in the page. You can get a better example of how power the library is by looking at their banner here... http://www.greensock.com/ it was all done using their library. I used this library in the past when I was doing Flash development, it was really great working with it. Made a lot of things easier, just like this class you wrote :)

I'm always a big fan of code that helps me write less code, so if you can get this up to the level of green sock you might even be able to sell it, I'd buy a copy :)

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

Tweening library is quite a work.
It might be better to create one on top of this module rather then extending it.
But basic tweening is affordable at this level, so will do something.

idealconceptz
User offline. Last seen 5 weeks 4 days ago. Offline
Joined: 2 Apr 2011

This looks very good, but I can't get it to cancel an individual timer.

I'm using

handle = th:newTimer(10000, myfunction )
handle:cancel()

is this correct?

SUHADA
User offline. Last seen 5 days 21 hours ago. Offline
Joined: 8 Jun 2011

Hi Lerg,
Thanks very much for your very useful pausable timer module.
I had a use where I wanted to know how much time had passed and how much time was remaining. So I wrote the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    -- Returns the two values: timesofar amd timeremaining
    function tH:getTimes ()
      local remainingTime
      if self.paused then
        remainingTime = self.remainingTime
      else
        local pausingTime = system.getTimer()
        remainingTime = self.remainingTime - (pausingTime - self.intervalStartTime)
        if remainingTime < 0 then
          remainingTime = 0
        end
      end
      return (self.duration - remainingTime), remainingTime
    end

and slotted it in after the pause function.

Note that it doesn't alter any variables other than those local to the function so can't introduce any bugs into your routines.

I haven't tested at different speeds!
Perhaps you'll know whether it will work at different speeds?

If you do decide to include in a later version then in honour of the "non-repeated code" principle you will note that it repeats some code from the pause function so you may wish to move the repeated lines out to another shared routine. I'll leave it in your good hands ;-)
Suhada

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

idealconceptz, it is correct. If you have any problems you can try to catch me on the IRC channel so we can discuss it.

SUHADA, thanks. I'll add a similar function in the next release. As for speed only the duration variable should be multiplied with the speed.

dellagd
User offline. Last seen 25 weeks 20 hours ago. Offline
Joined: 6 Feb 2011

Hello-

I am getting this error when using your code-
...awesome_timers_and_transitions_manager_from_lerg.lua:85: attempt to c
all field 'callback' (a table value)
stack traceback:
[C]: in function 'callback'
...awesome_timers_and_transitions_manager_from_lerg.lua:85: in function
'_listener'

Any idea what could cause that?

If this helps, I am declaring my callback function like this:

local xxxxxx = {}
function xxxxxx:timer (event)
--code
end

Thanks

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

Hi dellagd,

Table listeners aren't supported yet. You should use a closure
tnt:newTimer(1000, function (event) xxxxxx:timer(event) end)

Table callers will be in the next version.

theo1
User offline. Last seen 19 weeks 5 days ago. Offline
Joined: 26 Sep 2011

Hi, here's a nice little addition:

It allows you to queue up a series of transition parameters to be performed in sequence on the same display object. A list of parameter lists is passed to it. Only the onComplete from the final set of parameters is called, at the end of all the transitions.

Note that currently this is slightly inconsistent with your original code, as it does not (currently) make a local clone of the parameter data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-- Pausable transition sequence
-- @param object table An object for which transition is applied.
-- @param params_array table An array of transition parameters
function _M:newTransitionSequence(object, params_array)
 
        local tsH = {}
        tsH.params_array = params_array
        
        -- the only user onComplete we keep is
        -- the last one in the params array 
        local currentTransition = nil
        for i = 1,(#params_array-1) do
                params_array[i].onComplete = 
                        function(target)
                                currentTransition = _M:newTransition(object, params_array[i+1])
                        end
        end
        
        currentTransition = _M:newTransition(object, params_array[1])
        
        function tsH:cancel()
            currentTransition:cancel()
        end
    
        function tsH:pause()
            currentTransition:pause()
        end
    
        return tsH
end

Demo of use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
local trans_sequence = {
        { 
                time = 2000, 
                x = 10,
                y = 20,
                rotation = 360
        },
        {
                delay = 1000,
                transition = easing.easeIn,
                time = 3000,
                xScale = 0.001,
                yScale = 0.001
        },
        {
                time = 1000,
                alpha = 0
        }
}
 
tm:newTransitionSequence(my_sprite, trans_sequence)

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

theo1, thanks. That might be useful.
I will probably build a twinning library on top of this module.
In my development version I have ability to cycle transitions: repeat and "back and forth".

Pasz72's picture
Pasz72
User offline. Last seen 3 hours 24 min ago. Offline
Joined: 13 Jul 2011

Great work Lerg!

Can you explain how to use your file with something like this :

transition.to( obj,{ delay = 8500, time=1500, alpha=1.0, onComplete=nextPart } )

I use director of Ricardo Rauber, and sometimes when I change a scene, a transition of a former scenes executes.

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

pasztelfort, hi.
I couldn't understand what do you mean, but I guess 'delay' param causes you problems, It's not well supported by my module.
Try to use it like this instead:
tnt:newTimer(8500, function () tnt:newTransition(obj, {time = 1500, alpha = 1, onComplete = nextPart}) end)
If you have other questions you can find me on the IRC channel.

Pasz72's picture
Pasz72
User offline. Last seen 3 hours 24 min ago. Offline
Joined: 13 Jul 2011

Lerg > your guess was right ! I'm sorry I was not clear enough.

Your solution is very nice. Thanks.

zeeero.coool's picture
zeeero.coool
User offline. Last seen 1 week 6 days ago. Offline
Joined: 6 Jun 2011

When onComplete callback is fired inside :newTransition, userData inside the callback is "nil". I wonder if this is a bug or a work in progress . This happens when the callback is a method of an object. Otherwise, a regular function can access the userData without a problem.

Is there any news on this ?

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

zeeero.coool, userData in transitions is a bit misleading, because it doesn't return an "event" object, instead it returns the actual object being transitioned.
I don't how to proceed with it. Either uncomment lines in callbackWrapper function or don't use this functionality at all.

CELL's picture
CELL
User offline. Last seen 6 days 10 hours ago. Offline
Joined: 23 Jan 2011

Amazing work. Thank you Lerg

alanfalcon's picture
alanfalcon
User offline. Last seen 4 weeks 3 days ago. Offline
Joined: 16 Jan 2011

This is really great, thank you so much for sharing and keeping it updated, Lerg!

hytka81
User offline. Last seen 2 days 7 min ago. Offline
Joined: 27 Apr 2011

Hi!

Did something change with newTimer() in v1.2 because now I get errors with userData in my code:

... in function 'tH_callback' ... attempt to index field 'userData' (a nil value)

Line is as follows:

1
2
3
4
5
function spawnEnemy(event)
       local class = event.userData.enemyClass
.
.
.

It worked with the previous version the way I was using userData:

1
tnt:newTimer(e.spawnTime, spawnEnemy, 1, 'timer', { enemyClass = e.class, enemyName = e.name, enemyIndex = enemy_index, enemyWave = current_wave })

Solved:

I was little bit too hasty with posting here... This just shows how new I'm still to Lua but after looking into the code I realized that this is the way to do it now:

1
tnt:newTimer(e.spawnTime, spawnEnemy, 1, { userData = {enemyClass = e.class, enemyName = e.name, enemyIndex = enemy_index, enemyWave = current_wave} })

Lerg
User offline. Last seen 21 hours 15 min ago. Offline
Joined: 8 May 2011

Hi, hytka81,

Sorry, I forgot to mention that I've changed a bit the way how to deal with userData, but you figured this out!

TX
User offline. Last seen 13 hours 46 min ago. Offline
Joined: 3 Feb 2011

Thank you Lerg for all your work. Your reply #29 was very helpful too since I was previously using a delay with transition.to.