Difficulty cancelling transitions

When exiting a scene using the storyboard API, I cancel all the transitions using the technique described in http://www.coronalabs.com/blog/2011/08/15/corona-sdk-memory-leak-prevention-101/. Unfortunately, when there are more than a few transitions (it happens when creating more than one objects), several of them remain in memory and continue calling the onComplete functions. That is, some transitions are cancelled and others are not. This problem may be related to the way my variables are scoped and I’m hoping someone else can help spot the error in my reasoning.

The overview of the code is as such: a build function parses a table and checks the keys to determine what object creation function to call. For simplicity, only the problematic function is shown here. The object in the createMover function is built using the arguments passed in from the build function. During the creation of the object, transitions are created and added to a global transitionStash table. Basically, the ‘box’ moves back and forth, no sweat. When the scene exits, the cancelAllTransitions function is called. Looping through the transitionStash immediately after it has been cancelled reveals an empty table as expected; however, looping through it on the enterScene event for the following scene reveals several transitions that still exist. The transition continues after being cancelled when the object is returned (or not), when it is added to a table in the build function (or not), when that table in the build function is returned (or not). So, my usual tricks for checking scope are unable to resolve this issue. Please have a look and point out the error of my ways. :slight_smile:

[blockcode]
function scene:createScene(event)

group = self.view
staticGroup = display.newGroup()
group:insert(staticGroup)

local function createMover (x, y, x0, y0, xF, yF, v)
local box = display.newImageRect(“images/ice.png”, 64, 64)
box.x = x * unit + unit/2
box.uID = x * y
–Additional declarations redacted for clarity

local setup, goToEnd, goToStart

setup = function()
transitionStash[tostring(box.uID)] = transition.to(box, {time=box.offset*1000/box.speed, x = box.xF, y = box.yF, onComplete = goToStart})
end

goToEnd = function()
transitionStash[tostring(box.uID)] = transition.to(box, {time=box.distance*1000/box.speed, x = box.xF, y = box.yF, onComplete = goToStart})
end

goToStart = function()
transitionStash[tostring(box.uID)] = transition.to(box, {time=box.distance*1000/box.speed, x = box.x0, y = box.y0, onComplete = goToEnd})
end

setup()
return box
end

local function build(levelObject)
–[[ Pass a level table into this function. The table is iterated
for each type of drawn element and associated function ]]–
local movers = {}
for key,value in pairs(levelObject) do
if (key == “movingBoxes”) then
for _, val in ipairs(levelObject.movingBoxes) do
–Tried to add to table to no effect
table.insert(movers, createMover(val.x, val.y, val.x0, val.y0, val.xF, val.yF, 2))
staticGroup:insert(movers[#movers])
end
elseif (key == “pushBoxes”) then
for _, val in ipairs(levelObject.pushBoxes) do
–Works fine for transitionless objects if the creating function returns nothing here
createPushable(val.x, val.y)
end
end
end
end

end
function scene:exitScene(event)
local group = self.view
cancelAllTransitions()
cancelAllTimers()
end
[/blockcode]

It doesn’t matter if the box object is added to the staticGroup in the createMover function or from the table in the build function. Is it necessary to return the ‘box’ object and add it into a table in the build function? All the other object creating functions do not return the object and work fine.

 local function createPushable (x, y)  
 local push = display.newImageRect("images/ball.png", 64, 64)  
 push.x = x \* unit + unit/2  
 push.y = y \* unit + unit/2  
 push.direction = 4  
 push.myName = "sliding"  
 staticGroup:insert(push)  
 end  

Just for comfort, here’s the cancelAllTransitions function

[blockcode]
– GLOBAL DECLARATIONS (from main.lua)–
transitionStash = {}
function cancelAllTransitions()
local k, v

for k,v in pairs(transitionStash) do
transition.cancel( v )
v = nil; k = nil
end

transitionStash = nil
transitionStash = {}
end
[/blockcode]

The other objects created using similar functions to createMover (without transitions) get garbage collected correctly. Memory usage at the menu scene is always within 1k after a level except when the scene contained more than a few of these objects with transitions. Thank you in advance for helping fix this code! [import]uid: 168249 topic_id: 30194 reply_id: 330194[/import]

Just for giggles, try a slight variation

for tName in pairs(transitionStash) do   
 if transitionStash[tName] then transition.cancel(transitionStash[tName]); transitionStash[tName]=nil end  
end  
transitionStash=nil  
transitionStash={}  

That’s my code and it seems to work perfectly in my stuff (granted, I don’t use StoryBoard) [import]uid: 160496 topic_id: 30194 reply_id: 120918[/import]

Thanks for the timely reply Mike! Just tried that code and it behaves identically to the previous situation. Some of the transitions are cancelled and some still remain. I appreciate you having a look! [import]uid: 168249 topic_id: 30194 reply_id: 120922[/import]

Update: the overall problem still exists and seems to relate to the speed at which the transitions are cancelled. The runtime errors (from ‘cancelled’ transitions referencing removed display objects) were eliminated by a sloppy work around. First, forward declare an exitedScene variable and then check to see if it is true before starting a new transition.

--Modification of function createMover from above  
local function spin(delta)  
 if exitedScene ~= true then  
 local options = {time=100, rotation = box.rotation + delta}  
 box.transition = transition.to(box, options)  
 end  
end  
--Changed functions to properties of object  
box.spin = spin  

If that exited variable is reset to ‘false’ on the enterScene event, the transitions do not load. This happens because the enterScene is fired after the createScene event as expected. The workaround for that has been to remove the scene so it is always recreated (along with the exitedScene variable).

The memory usage still behaves strangely and the transitions still exist even when the objects are removed. If a print statement is placed in the transition functions, they will continue to output the prints for several seconds after switching to another scene. Now that the transitions are attached to the object, it isn’t necessary to call cancelAllTransitions and the transitions eventually go away (1 or 2 cycles). However, if the scene is repeatedly reloaded (think restarting a level) before the transitions have cancelled, memory usage grows stepwise and eventually returns to the original value.

Are there better ways to manage the delay in cancelling transitions? It seems that stopping them all before the scene exits would be ideal and that the current method is sloppy. Is there a way to delay the exitScene event or calling of the next scene while waiting for the current one to clean up? What affects the speed at which transitions are cancelled? One last thing, it doesn’t appear to make any difference if the createMover function returns the new object or not. Thanks again for any additional suggestions! [import]uid: 168249 topic_id: 30194 reply_id: 121050[/import]

Hi cooperlabs, I’m having the same problem in my game which I made a thread for here. We were unable to reach a conclusion though and unfortunately I don’t have the time to properly debug it, I hope you’ll have more luck. [import]uid: 129450 topic_id: 30194 reply_id: 121468[/import]

Thanks info583 for the response. I’m disappointed to hear you are having a similar problem. It sounds like I’m lucky that my transitions eventually cancel or expire. In lieu of using a global or flag for exitedScene, it’s possible to use an object specific test. Since moving everything to a module, I’ve refactored the code a bit and became unable to use the exitedScene flag. That has been replaced with a query of a display-object specific feature. For example, the object.rotation or object.isVisible methods are only available on objects that haven’t been removed by Storyboard or display.remove. By querying an object property or method, there is no longer any dependence on an external variable. This step only prevents calling transitions that reference a deleted object. If your transition takes an hour and doesn’t clear, this isn’t likely to help. :wink:

local function spin(delta)  
 if box.isVisible ~= nil then --Check a property before calling a NEW transition  
 local options = {time=100, rotation = box.rotation + delta}  
 box.transition = transition.to(box, options)  
 end  
end  

You may or may not be able to use the isVisible property so try using another. It feels much cleaner having methods and ‘tests’ attached to an object instead of floating around in the global namespace. While it’s disappointing that these workarounds are necessary, it has been possible to get my code in line and I hope others can do so as well. Best of luck! [import]uid: 168249 topic_id: 30194 reply_id: 121486[/import]

This may be a complete wild-goose/red herring, but I remember reading somewhere that when stopping (some API’s), the onComplete fires. I’m pretty sure that had to do with audio.play as I can’t find the reference in the transition.to documents. But that might be the behavior and it makes sense. If you’ve tied future activities to the onComplete, stopping the transition means maybe going on to the next step.

I don’t have time to test this theory, but I do protect against it with a simple flag similar to this:

local slideRight  
local moveBlock = true  
  
local function slideLeft()  
 if not moveBlock then return true end  
 transition.to(block,{time=2000, x=200, onComplete=slideRight})  
end  
  
slideRight = function()  
 if not moveBlock then return true end  
 transition.to(block,{time=2000, x=400, onComplete=slideLeftt})  
end  

Then when I’m done, set moveBlock to false, the routine won’t start up the next transition. Now in my simple case, the one transition will continue to go until it’s up, but that shouldn’t be a problem and if you cancel it, even if the onComplete fires, the next one wont.

Again, this could be a total red herring. [import]uid: 19626 topic_id: 30194 reply_id: 121496[/import]

Thanks for the suggestion Rob! It doesn’t matter if it’s a red herring or not. The resolution to the problem seems the same: set a flag, do a test, check before proceeding. What remains interesting is that some transitions cancel and others proceed to the onComplete. That’s what led me down the road of thinking it was slow to cancel (on the order of a second or so). Irrespective of the cause, I will continue to check the status of the object before creating such ‘looping’ transitions and am glad to hear others have found the same.

Best regards! [import]uid: 168249 topic_id: 30194 reply_id: 121498[/import]

Just for giggles, try a slight variation

for tName in pairs(transitionStash) do   
 if transitionStash[tName] then transition.cancel(transitionStash[tName]); transitionStash[tName]=nil end  
end  
transitionStash=nil  
transitionStash={}  

That’s my code and it seems to work perfectly in my stuff (granted, I don’t use StoryBoard) [import]uid: 160496 topic_id: 30194 reply_id: 120918[/import]

Thanks for the timely reply Mike! Just tried that code and it behaves identically to the previous situation. Some of the transitions are cancelled and some still remain. I appreciate you having a look! [import]uid: 168249 topic_id: 30194 reply_id: 120922[/import]

Update: the overall problem still exists and seems to relate to the speed at which the transitions are cancelled. The runtime errors (from ‘cancelled’ transitions referencing removed display objects) were eliminated by a sloppy work around. First, forward declare an exitedScene variable and then check to see if it is true before starting a new transition.

--Modification of function createMover from above  
local function spin(delta)  
 if exitedScene ~= true then  
 local options = {time=100, rotation = box.rotation + delta}  
 box.transition = transition.to(box, options)  
 end  
end  
--Changed functions to properties of object  
box.spin = spin  

If that exited variable is reset to ‘false’ on the enterScene event, the transitions do not load. This happens because the enterScene is fired after the createScene event as expected. The workaround for that has been to remove the scene so it is always recreated (along with the exitedScene variable).

The memory usage still behaves strangely and the transitions still exist even when the objects are removed. If a print statement is placed in the transition functions, they will continue to output the prints for several seconds after switching to another scene. Now that the transitions are attached to the object, it isn’t necessary to call cancelAllTransitions and the transitions eventually go away (1 or 2 cycles). However, if the scene is repeatedly reloaded (think restarting a level) before the transitions have cancelled, memory usage grows stepwise and eventually returns to the original value.

Are there better ways to manage the delay in cancelling transitions? It seems that stopping them all before the scene exits would be ideal and that the current method is sloppy. Is there a way to delay the exitScene event or calling of the next scene while waiting for the current one to clean up? What affects the speed at which transitions are cancelled? One last thing, it doesn’t appear to make any difference if the createMover function returns the new object or not. Thanks again for any additional suggestions! [import]uid: 168249 topic_id: 30194 reply_id: 121050[/import]

Hi cooperlabs, I’m having the same problem in my game which I made a thread for here. We were unable to reach a conclusion though and unfortunately I don’t have the time to properly debug it, I hope you’ll have more luck. [import]uid: 129450 topic_id: 30194 reply_id: 121468[/import]

Thanks info583 for the response. I’m disappointed to hear you are having a similar problem. It sounds like I’m lucky that my transitions eventually cancel or expire. In lieu of using a global or flag for exitedScene, it’s possible to use an object specific test. Since moving everything to a module, I’ve refactored the code a bit and became unable to use the exitedScene flag. That has been replaced with a query of a display-object specific feature. For example, the object.rotation or object.isVisible methods are only available on objects that haven’t been removed by Storyboard or display.remove. By querying an object property or method, there is no longer any dependence on an external variable. This step only prevents calling transitions that reference a deleted object. If your transition takes an hour and doesn’t clear, this isn’t likely to help. :wink:

local function spin(delta)  
 if box.isVisible ~= nil then --Check a property before calling a NEW transition  
 local options = {time=100, rotation = box.rotation + delta}  
 box.transition = transition.to(box, options)  
 end  
end  

You may or may not be able to use the isVisible property so try using another. It feels much cleaner having methods and ‘tests’ attached to an object instead of floating around in the global namespace. While it’s disappointing that these workarounds are necessary, it has been possible to get my code in line and I hope others can do so as well. Best of luck! [import]uid: 168249 topic_id: 30194 reply_id: 121486[/import]

This may be a complete wild-goose/red herring, but I remember reading somewhere that when stopping (some API’s), the onComplete fires. I’m pretty sure that had to do with audio.play as I can’t find the reference in the transition.to documents. But that might be the behavior and it makes sense. If you’ve tied future activities to the onComplete, stopping the transition means maybe going on to the next step.

I don’t have time to test this theory, but I do protect against it with a simple flag similar to this:

local slideRight  
local moveBlock = true  
  
local function slideLeft()  
 if not moveBlock then return true end  
 transition.to(block,{time=2000, x=200, onComplete=slideRight})  
end  
  
slideRight = function()  
 if not moveBlock then return true end  
 transition.to(block,{time=2000, x=400, onComplete=slideLeftt})  
end  

Then when I’m done, set moveBlock to false, the routine won’t start up the next transition. Now in my simple case, the one transition will continue to go until it’s up, but that shouldn’t be a problem and if you cancel it, even if the onComplete fires, the next one wont.

Again, this could be a total red herring. [import]uid: 19626 topic_id: 30194 reply_id: 121496[/import]

Thanks for the suggestion Rob! It doesn’t matter if it’s a red herring or not. The resolution to the problem seems the same: set a flag, do a test, check before proceeding. What remains interesting is that some transitions cancel and others proceed to the onComplete. That’s what led me down the road of thinking it was slow to cancel (on the order of a second or so). Irrespective of the cause, I will continue to check the status of the object before creating such ‘looping’ transitions and am glad to hear others have found the same.

Best regards! [import]uid: 168249 topic_id: 30194 reply_id: 121498[/import]

EDIT: Ignore this post. :slight_smile: See my next post below where it is solved for me…

ok, in a late night epiphany followed by furious tinkering and recoding, I think I figured this out. At least the solution I’ve implemented is working for me.

So the issue had to do with scope for me, with the addition of a check to see if the transition was running or not.

So this is what solved the errors related to transitions still firing, appearing to have a delay in cancel, and/or firing the onComplete when canceled - all theories from posts above.

First, I declared the var for any transition in the scene as local but outside of the main scene:createScene function (using Storyboard). This appeared to be crucial so that they are fully accessible later from within the scene:exitScene function for cleanup on scene change. Still local to the scene and not global, just declared outside the scene:createScene function. 

I then made sure all transition tweenReferences inside the main scene function, or at any levels deeper into nested functions, are NOT declared local. They are then treated as local to the scene but at the highest level of the scene, as described in previous sentence.

Then in the scene:exitScene function, call the transition.cancel( tweenReference ). But this still left the issue of nil reference errors on that transition when trying to cancel transitions that aren’t playing. So I just added a simple bit for all the tweenReferences within the scene:

if myAnim then transition.cancel( myAnim ) end myAnim = nil

Repeat inside the scene:exitScene function for all transitions and references.

This seems to have cleared up all errors and issues related to transitions continuing to fire or triggering the onComplete when trying to cancel them. I think that the idea that it was triggering the onComplete, first introduced above was actually an illusion. It was just that the transition wasn’t properly being canceled.

The only way to do that is to have proper scope access to it, check if it’s running/not nil, and then cancel it. If it’s not running, it doesn’t need to be canceled. The reference will still be nil’d and you’re all set.

A fairly simple fix to what seemed like a complex problem at the time. :slight_smile: Damn those simple scope issues!

Here’s a quick code example:

---------------------------------------------------------------------------------- -- -- scene1.lua -- ---------------------------------------------------------------------------------- local storyboard = require( "storyboard" ) local scene = storyboard.newScene() --declare any transition references outside main scene function local myAnim function scene:createScene( event ) local myObject = display.newRect(0,0,200,200) myObject:setFillColor(255,255,255) -- two functions, anim fires and then triggers finish onComplete local function finish() myObject.isVisible = false end local function anim() myAnim = transition.to( myObject, {time=5000, x=500, y=500, onComplete=finish} ) end anim() function touchIt() if event.phase == "began" then storyboard.state.fromActivity = true storyboard.gotoScene( "menu", "crossFade", 1000 ) end return true end myObject:addEventListener("touch", touchIt) end function scene:exitScene( event ) if myAnim then transition.cancel( myAnim ) end myAnim = nil display.remove( myObject ) myObject = nil end

EDIT: Ignore this post. :slight_smile: See my next post below where it is solved for me…

ok, in a late night epiphany followed by furious tinkering and recoding, I think I figured this out. At least the solution I’ve implemented is working for me.

So the issue had to do with scope for me, with the addition of a check to see if the transition was running or not.

So this is what solved the errors related to transitions still firing, appearing to have a delay in cancel, and/or firing the onComplete when canceled - all theories from posts above.

First, I declared the var for any transition in the scene as local but outside of the main scene:createScene function (using Storyboard). This appeared to be crucial so that they are fully accessible later from within the scene:exitScene function for cleanup on scene change. Still local to the scene and not global, just declared outside the scene:createScene function. 

I then made sure all transition tweenReferences inside the main scene function, or at any levels deeper into nested functions, are NOT declared local. They are then treated as local to the scene but at the highest level of the scene, as described in previous sentence.

Then in the scene:exitScene function, call the transition.cancel( tweenReference ). But this still left the issue of nil reference errors on that transition when trying to cancel transitions that aren’t playing. So I just added a simple bit for all the tweenReferences within the scene:

if myAnim then transition.cancel( myAnim ) end myAnim = nil

Repeat inside the scene:exitScene function for all transitions and references.

This seems to have cleared up all errors and issues related to transitions continuing to fire or triggering the onComplete when trying to cancel them. I think that the idea that it was triggering the onComplete, first introduced above was actually an illusion. It was just that the transition wasn’t properly being canceled.

The only way to do that is to have proper scope access to it, check if it’s running/not nil, and then cancel it. If it’s not running, it doesn’t need to be canceled. The reference will still be nil’d and you’re all set.

A fairly simple fix to what seemed like a complex problem at the time. :slight_smile: Damn those simple scope issues!

Here’s a quick code example:

---------------------------------------------------------------------------------- -- -- scene1.lua -- ---------------------------------------------------------------------------------- local storyboard = require( "storyboard" ) local scene = storyboard.newScene() --declare any transition references outside main scene function local myAnim function scene:createScene( event ) local myObject = display.newRect(0,0,200,200) myObject:setFillColor(255,255,255) -- two functions, anim fires and then triggers finish onComplete local function finish() myObject.isVisible = false end local function anim() myAnim = transition.to( myObject, {time=5000, x=500, y=500, onComplete=finish} ) end anim() function touchIt() if event.phase == "began" then storyboard.state.fromActivity = true storyboard.gotoScene( "menu", "crossFade", 1000 ) end return true end myObject:addEventListener("touch", touchIt) end function scene:exitScene( event ) if myAnim then transition.cancel( myAnim ) end myAnim = nil display.remove( myObject ) myObject = nil end