Universal Test for Valid Object

Hey folks.  I’ve run into an interesting problem (which may seem simple on first glance). 

I already filed a bug (38656) for this, but I like to self-solve as much as possible and I’m guessing someone out there may have a nice solution or at least some ideas.

Note: I did find a workaround that will work universally, but I’m still hoping Corona will fix this weird behavior and/or provide a ‘valid object’ test.  See my third post below for the workaround.

Problem Recipe

  • Create an object
  • Add a physics body.
  • Add enterFrame listener ‘method’ to body.
  • Start Listener
  • Using timer.performWithDelay() destroy object after a short duration.

Problem That Occurs

On the first ‘enterFrame’ after the timer fires:

  • self.removeSelf still points to a function
  • self.setLinearVelocity (and all other physics methods) is nil

On the second ‘enterFrame’ after the timer fires:

  • self.removeSelf is nil
  • self.setLinearVelocity (and all other physics methods) is nil

So, why is this a problem?

  1. Corona does not provide a ‘is valid’ test for objects.
  2. The generally accepted way to test if an object is valid is by checking for the existence of a ‘removeSelf’ function.

Workarounds?

Some of you may be thinking one of the following things:

  • Workaround 1 - “Duh, Ed.  Just set the object to nil after removing it.”
  • Workaround 2 - “Um, you spelled out the solution in your problem definition.  Check for the presence of a physics method like ‘setLinearVelocity’ instead!”

Why Workaround Are No Good

  • Workaround 1 - Actually this is good, but only in the same scope.  For any case where an object may be removed in a different scope, the solution fails.
  • Workaround 2 - Yes, this is workable and solves the problem, BUT it is not universal.  I need a univesal fix. (Thus the bug filing).

So, if anyone out there has solutions or ideas please share them.  Meanwhile, here is some code to show the problem (you’ll need to look at the console output to see the issue)

local physics = require "physics" physics.start() physics.setGravity( 0, 0 ) local testObj = display.newCircle( 100, 100, 10 ) physics.addBody( testObj ) timer.performWithDelay( 1000, function() display.remove( testObj ) print("Object destroyed @ " .. tostring( system.getTimer())) end ) testObj.enterFrame = function( self, event ) if( self.removeSelf == nil ) then Runtime:removeEventListener( "enterFrame", self ) print( "Ignoring " .. tostring( self ), tostring( self.setLinearVelocity ), system.getTimer(), "\n-----------" ) return end print( tostring( self ), tostring( self.setLinearVelocity ), system.getTimer(), "\n-----------" ) end Runtime:addEventListener( "enterFrame", testObj )

Hi again.  I forgot to mention, I have tried fixing this as follows, but it doesn’t work:

timer.performWithDelay( 1000, function() display.remove( testObj ) testObj.removeSelf = nil -- FORCE IT print("Object destroyed @ " .. tostring( system.getTimer())) end )

Even when I force ‘removeSelf’ to nil, the enterFrame will receive an object with ‘removeSelf’ pointing to a function on the first frame.  It is almost as if, ‘enterFrame’ is receiving a cached reference to the object.  It is very puzzling.

Run this code to see what I mean:

local physics = require "physics" physics.start() physics.setGravity( 0, 0 ) local testObj = display.newCircle( 100, 100, 10 ) physics.addBody( testObj ) timer.performWithDelay( 1000, function() display.remove( testObj ) testObj.removeSelf = nil testObj.testField = "removed @ " .. system.getTimer() print("Object destroyed @ " .. tostring( system.getTimer())) end ) testObj.enterFrame = function( self, event ) if( self.removeSelf == nil ) then Runtime:removeEventListener( "enterFrame", self ) print( "Ignoring " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ) .. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) return end print( "enteFrame() " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ).. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) end Runtime:addEventListener( "enterFrame", testObj )

Produces this:

----------- Object destroyed @ 1034 enteFrame() table: 04B464D8 || nil || removed @ 1034 || 1034 ----------- Ignoring table: 04B464D8 || nil || removed @ 1034 || 1062 -----------

And this is why I always tell people to be descriptive when asking for help as well as to show code…

My last post provided me with a solution.  I’ll paste it below for those who also have encountered or may encounter this problem.

The solution is simple.  Marking a ‘flag’ of your own instead of testing ‘removeSelf’ ensures same frame recognition of a ‘invalid object’.

I am still puzzled why ‘physics stuff’ is removed first, then one frame later ‘removeSelf’ goes nil, but I can live this this for now:

local physics = require "physics" physics.start() physics.setGravity( 0, 0 ) local testObj = display.newCircle( 100, 100, 10 ) physics.addBody( testObj ) timer.performWithDelay( 1000, function() display.remove( testObj ) testObj.removeSelf = nil testObj.testField = "removed @ " .. system.getTimer() testObj.destroyed = true print("Object destroyed @ " .. tostring( system.getTimer())) end ) testObj.enterFrame = function( self, event ) if( self.destroyed ) then Runtime:removeEventListener( "enterFrame", self ) print( "Ignoring " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ) .. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) return end print( "enteFrame() " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ).. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) end Runtime:addEventListener( "enterFrame", testObj )

Execute this code early in your project to add ‘isValid()’ test to display library.

-- display.remove( obj) - Replacement that works in tandem with 'isValid' -- display.\_remove = display.remove display.remove = function ( obj ) display.\_remove( obj ) obj.\_\_destroyed = true end -- display.isValid( obj ) - Is valid displayObject -- display.isValid = function ( obj ) return( obj and not obj.\_\_destroyed ) end

in general i don’t consider it “safe” to test for an object’s internal state by querying existence of internal methods because they’re all “virtual” (that is, they won’t show up via:   k,v in pairs(obj) print(k,v) end) and you don’t have any real control over when the meta is torn down internally.

physics is an exception, because it’s a separate subsystem, unrelated to EventListener, so can be torn down immediately - that is, there’s never an “enterFrame” callback pending on the actual physics body, for example.  but just about everything else “useful” in the display descends from EventListener, and having events registered will “store” a reference that may be causing something to linger a bit longer than expected in order to callback the object on that currently pending event.

if i understand your problem, you could probably improve stability by calling collectgarbage(“collect”) immediately after your timer’s display.remove(testObj) – good chance then that by the time enterFrame fires, object will have been fully disposed, so removeSelf will appear nil in a more “synchronous” manner.

@Dave,

Interesting idea re: collection.  I’ll give it a try …

local physics = require "physics" physics.start() physics.setGravity( 0, 0 ) local testObj = display.newCircle( 100, 100, 10 ) physics.addBody( testObj ) testObj.timer = function( self ) self.testField = "removed @ " .. system.getTimer() print("Object destroyed @ " .. tostring( system.getTimer())) display.remove( self ) self = nil collectgarbage("collect") end timer.performWithDelay( 1000, testObj ) testObj.enterFrame = function( self, event ) if( self.removeSelf == nil ) then Runtime:removeEventListener( "enterFrame", self ) print( "Ignoring " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ) .. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) return end print( "enteFrame() " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ).. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) end Runtime:addEventListener( "enterFrame", testObj ) testObj = nil

yep, that works, but its a bit of a performance killer when there are lots of removes going on.

For now, I’ll live with setting a flag which is relatively light-weight and hold out for a Corona change and/or addition to make this safer.

Thanks!

Only reason I suggested collectgarbage was that you had given the right answer in your OP, but discounted it, so I was admittedly grasping at straws.

Perhaps i still don’t fully understand your usage, so forgive me for saying so, but it all seems a bit “contrived”. (and I’m trying really hard to not come across as using that “Duh Ed” tone :smiley: but i’ll probably fail, so apologies).  That is, you partially dispose of an object, but leave its event listener in effect, then “wonder” why your object wasn’t fully disposed when you see it again in the event listener.  (A: because you left its event listener.)

Maybe it’s just the structure of this demo, as I understand it’s not always easy to construct a “simple” test to convey intended usage.  Still, and in general, if you fully and properly dispose of things when you intend to, then you should never need an after-the-fact “are you still valid?” test.

But nil’ing and properly disposing isn’t merely a “work around”, it’s the solution.  i don’t get your objection to it based on scope, as scope is of your own making, so alter it if/as necessary.  For example:  store your objects in a container table, each with a reference back to their respective container, and they’ll be able to “nil themselves” out of their container without any regard to scope.  Etc.

If you do all that, display.remove + removeEventListener + object.container[object.key]=nil, in your timer then you won’t ever receive that trailing enterFrame event, let alone have to query the object for “isValid”.

We recently added object introspection  to allow better debugging of objects. I don’t know if it will be helpful or not but see:

http://docs.coronalabs.com/api/type/DisplayObject/properties.html

Rob

@Rob,

Thanks.  That is a pretty cool addition.  I immediately started thinking interesting ways to harness the introspection features for debugging.  

Cheers,

Ed

@Dave,

Thanks First

Let me say “Thank You.”   I should have started off with that previously.  I’m afraid I’m not good at setting the proper tone in replies and may have come across as dismissive when I was just not doing a good job of describing my usages.

So, to be clear, I am very thankful for any and all responses.  I very much appreciate the time it takes to answer.

Contrived my example was…

You are correct.  The example I gave was very contrived.  You are also correct when you say " I understand it’s not always easy to construct a “simple” test to convey intended usage. "

My examples was designed to run in a single file to demonstrate the sequence of events, rather than the problem.

In my real apps and games I do the following:

  • Create objects in different files.

  • Add these objects to the same hierarchy of groups. (I use groups for display layering and all layers have a common parent group for easy destruction.)

  • Instead of maintaining tables of specific objects types for task purposes, I attach Runtime listeners to them and dispatch custom events (see example below).

  • Later, I will destroy the parent group and simultaneously, destroy all of the objects in the group.  At this point, my problem occurs with any and all Runtime listeners (including ‘enterFrame’).  They basically dangle for one frame (or more?) till the object is cleaned up.  

Arbitrary Example of Custom Runtime Events

Module boomer.lua:

local public = {} local function makeBoomer( group ) local obj = display.newCircle( group, 100, 100, 10 ) obj.onBoom = function( self, event ) transition.to( self, { xScale = 10, yScale = 10, alpha = 0, onComplete = display.remove } ) end Runtime:addEventListener( "onBoom", obj ) end public.create = makeBoomer return public

Elsewhere…

local boomer = require "boomer" local tmp = display.newGroup boomer.create( tmp ) display.remove( tmp ) tmp = nil timer.performWithDelay( 1, function() Runtime:dispatchEvent( "onBoom" ) end )

In the above example, the code does not function as I would wish it to.  Instead, the ‘onBoom’ event still runs.

Solutions

Some solutions are:

  • Create a custom clean up event and call it. - i.e. Use the same event system and have the ‘cleanup’ function remove all listeners from the object and then destroy it.
    • Good because - This would solve the problem and ensure no leaks and no event firing after destruction.
    • Bad because - Complicated to code and maintain and will fail the first time something gets destroyed before the event is first fired.  This is basically the same as tracking objects manually and cleaning them up in terms of complexity and ugliness.
  • Similarly (to above), use the ‘finalize’ event which is now always called regardless of how an object is destroyed (kudos to Corona).
    • Good because - This is clean and part of the standard API.  It is also guaranteed, thus solving one issue with the prior solution.
    • Bad because - This is still a lot of work to maintain for a lazy coder like me.  (Still, I did jump on the bandwagon and ask this feature be fixed, so “Thanks again to the Corona staff!”)
  • My flag method - This fits easily into my own method of coding and solves the problem as long as I check for the flag, which is a one-liner in my code:
    • if( autoIgnore( “enterFrame”, self ) ) then return end
    • The above line is a helper function of mine that does this:
      • Checks if the object was marked destroyed, if not returns false, if so continues
      • Removes the named event listener from the objects
      • Returns true
    • I simply put the above line in my listener definitions at the top and they automatically get cleaned and never run for destroyed objects.   Easy-peasy.

Here is the above example with my custom Runtime shorthand functions:

Shorthand helpers:

\_G.listen = function( name, listener ) Runtime:addEventListener( name, listener ) end \_G.ignore = function( name, listener ) Runtime:removeEventListener( name, listener ) end \_G.autoIgnore = function( name, obj ) if( obj.\_\_destroyed or obj.removeSelf == nil ) then ignore( name, obj ) obj[name] = nil return true end return false end \_G.post = function( name, params, debuglvl ) local params = params or {} local event = { name = name } for k,v in pairs( params ) do event[k] = v end if( not event.time ) then event.time = system.getTimer() end Runtime:dispatchEvent( event ) end

boomer.lua (2.0)

local public = {} local function makeBoomer( group ) local obj = display.newCircle( group, 100, 100, 10 ) obj.onBoom = function( self, event ) if( autoIgnore( "onBoom", self) ) then return end transition.to( self, { xScale = 10, yScale = 10, alpha = 0, onComplete = display.remove } ) end listen( "onBoom", obj ) end public.create = makeBoomer return public

local boomer = require "boomer" local tmp = display.newGroup boomer.create( tmp ) display.remove( tmp ) tmp = nil timer.performWithDelay( 1, function() post( "onBoom" ) end )

This time, all is well.

>> In the above example, the code does not function as I would wish it to.  Instead, the ‘onBoom’ event still runs.

Tho it does behave exactly as per docs (and exactly as i would expect), fe (and elsewhere):  http://docs.coronalabs.com/daily/guide/events/detectEvents/index.html#removing-event-listeners

So, it would seem that the crux of your matter is that you’re hoping display.remove (in your transition’s onComplete) will also remove stuff from Runtime.  There are good reasons why runtime events persist, so I wouldn’t expect that behavior to change (at least not without angering developers by breaking existing code)

>>Some solutions are:

Somewhere amidst the first and second is an even simpler approach, that doesn’t further compound the issue by using events or finalize.  Why not just create your own more elaborate removal function and just call it _ directly _ onComplete, replacing the call to display.remove, but wrapping that functionality too?  roughly fe:

transition.to( …whatever…  onComplete=myCustomerRemover})

function myCustomRemover(obj)

  obj._functionListeners = nil – this is hackery, but unfortunately there’s no formal: obj:removeEventListener("*")

  obj._tableListeners = nil – ditto, or you could instead pairs() through the obj looking for “on*()” and remove those by name?

  – maybe even:  transition.cancel(obj) and whatever ELSE might need cleanup

  display.remove(obj) – finally, duplicate original functionality

end

…and done!   :slight_smile:

[Edit:  nope, not quite done.  :D  I hadn’t actually tried that “hackery” in a while, but seems to no longer work – as such hackery is wont to do.  (Maybe someone else knows the current version of that hack, if it still exists in some form?)  At any rate, ignore as written, tho you could still perhaps do a pairs() search as in comment.  I still think a direct call to your own removal function would be the most straightforward approach.]

revisit:  it occurs to me that i missed your point - killing off the event before it fires, not killing off the object at end of event.  oh well, i should just be quiet now, as the “let corona do everything automatically” approach is essentially the opposite of how i work:  if i create an object that registers an event, it is also responsible for removing it, and is stored in my own lists (and don’t rely on display groups for “ownership” OR disposal), etc.  and instead of adding “helper” routines to try to patch all the edge-cases where doing stuff automatically fails, i have a helper that can verify that a group is EMPTY prior to removal (otherwise that’s a sign i’ve forgotten to code something somewhere).  i also reference count my create/destroy’s, and tag objects with unique keys (can print during debug if something is found to be “lingering” to help track them down).  sounds like a lot of work?  it’s really not - what sounds like a lot of work to me is constantly trying to hunt the bizarre “doesn’t work automatically” stuff like this.  my personal motto:  do it all explicitly and know it was done (and when), rather than implictly and hope.  i’m not saying my way is “better” or trying to recruit you to my camp, but just as reason why my tool kit doesn’t have the right kind of duct tape in it for this.

Hi again.  I forgot to mention, I have tried fixing this as follows, but it doesn’t work:

timer.performWithDelay( 1000, function() display.remove( testObj ) testObj.removeSelf = nil -- FORCE IT print("Object destroyed @ " .. tostring( system.getTimer())) end )

Even when I force ‘removeSelf’ to nil, the enterFrame will receive an object with ‘removeSelf’ pointing to a function on the first frame.  It is almost as if, ‘enterFrame’ is receiving a cached reference to the object.  It is very puzzling.

Run this code to see what I mean:

local physics = require "physics" physics.start() physics.setGravity( 0, 0 ) local testObj = display.newCircle( 100, 100, 10 ) physics.addBody( testObj ) timer.performWithDelay( 1000, function() display.remove( testObj ) testObj.removeSelf = nil testObj.testField = "removed @ " .. system.getTimer() print("Object destroyed @ " .. tostring( system.getTimer())) end ) testObj.enterFrame = function( self, event ) if( self.removeSelf == nil ) then Runtime:removeEventListener( "enterFrame", self ) print( "Ignoring " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ) .. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) return end print( "enteFrame() " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ).. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) end Runtime:addEventListener( "enterFrame", testObj )

Produces this:

----------- Object destroyed @ 1034 enteFrame() table: 04B464D8 || nil || removed @ 1034 || 1034 ----------- Ignoring table: 04B464D8 || nil || removed @ 1034 || 1062 -----------

And this is why I always tell people to be descriptive when asking for help as well as to show code…

My last post provided me with a solution.  I’ll paste it below for those who also have encountered or may encounter this problem.

The solution is simple.  Marking a ‘flag’ of your own instead of testing ‘removeSelf’ ensures same frame recognition of a ‘invalid object’.

I am still puzzled why ‘physics stuff’ is removed first, then one frame later ‘removeSelf’ goes nil, but I can live this this for now:

local physics = require "physics" physics.start() physics.setGravity( 0, 0 ) local testObj = display.newCircle( 100, 100, 10 ) physics.addBody( testObj ) timer.performWithDelay( 1000, function() display.remove( testObj ) testObj.removeSelf = nil testObj.testField = "removed @ " .. system.getTimer() testObj.destroyed = true print("Object destroyed @ " .. tostring( system.getTimer())) end ) testObj.enterFrame = function( self, event ) if( self.destroyed ) then Runtime:removeEventListener( "enterFrame", self ) print( "Ignoring " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ) .. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) return end print( "enteFrame() " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ).. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) end Runtime:addEventListener( "enterFrame", testObj )

Execute this code early in your project to add ‘isValid()’ test to display library.

-- display.remove( obj) - Replacement that works in tandem with 'isValid' -- display.\_remove = display.remove display.remove = function ( obj ) display.\_remove( obj ) obj.\_\_destroyed = true end -- display.isValid( obj ) - Is valid displayObject -- display.isValid = function ( obj ) return( obj and not obj.\_\_destroyed ) end

in general i don’t consider it “safe” to test for an object’s internal state by querying existence of internal methods because they’re all “virtual” (that is, they won’t show up via:   k,v in pairs(obj) print(k,v) end) and you don’t have any real control over when the meta is torn down internally.

physics is an exception, because it’s a separate subsystem, unrelated to EventListener, so can be torn down immediately - that is, there’s never an “enterFrame” callback pending on the actual physics body, for example.  but just about everything else “useful” in the display descends from EventListener, and having events registered will “store” a reference that may be causing something to linger a bit longer than expected in order to callback the object on that currently pending event.

if i understand your problem, you could probably improve stability by calling collectgarbage(“collect”) immediately after your timer’s display.remove(testObj) – good chance then that by the time enterFrame fires, object will have been fully disposed, so removeSelf will appear nil in a more “synchronous” manner.

@Dave,

Interesting idea re: collection.  I’ll give it a try …

local physics = require "physics" physics.start() physics.setGravity( 0, 0 ) local testObj = display.newCircle( 100, 100, 10 ) physics.addBody( testObj ) testObj.timer = function( self ) self.testField = "removed @ " .. system.getTimer() print("Object destroyed @ " .. tostring( system.getTimer())) display.remove( self ) self = nil collectgarbage("collect") end timer.performWithDelay( 1000, testObj ) testObj.enterFrame = function( self, event ) if( self.removeSelf == nil ) then Runtime:removeEventListener( "enterFrame", self ) print( "Ignoring " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ) .. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) return end print( "enteFrame() " .. tostring( self ) .. " || " .. tostring( self.setLinearVelocity ).. " || " .. tostring( self.testField ) .. " || " .. system.getTimer() .. "\n-----------" ) end Runtime:addEventListener( "enterFrame", testObj ) testObj = nil

yep, that works, but its a bit of a performance killer when there are lots of removes going on.

For now, I’ll live with setting a flag which is relatively light-weight and hold out for a Corona change and/or addition to make this safer.

Thanks!

Only reason I suggested collectgarbage was that you had given the right answer in your OP, but discounted it, so I was admittedly grasping at straws.

Perhaps i still don’t fully understand your usage, so forgive me for saying so, but it all seems a bit “contrived”. (and I’m trying really hard to not come across as using that “Duh Ed” tone :smiley: but i’ll probably fail, so apologies).  That is, you partially dispose of an object, but leave its event listener in effect, then “wonder” why your object wasn’t fully disposed when you see it again in the event listener.  (A: because you left its event listener.)

Maybe it’s just the structure of this demo, as I understand it’s not always easy to construct a “simple” test to convey intended usage.  Still, and in general, if you fully and properly dispose of things when you intend to, then you should never need an after-the-fact “are you still valid?” test.

But nil’ing and properly disposing isn’t merely a “work around”, it’s the solution.  i don’t get your objection to it based on scope, as scope is of your own making, so alter it if/as necessary.  For example:  store your objects in a container table, each with a reference back to their respective container, and they’ll be able to “nil themselves” out of their container without any regard to scope.  Etc.

If you do all that, display.remove + removeEventListener + object.container[object.key]=nil, in your timer then you won’t ever receive that trailing enterFrame event, let alone have to query the object for “isValid”.

We recently added object introspection  to allow better debugging of objects. I don’t know if it will be helpful or not but see:

http://docs.coronalabs.com/api/type/DisplayObject/properties.html

Rob

@Rob,

Thanks.  That is a pretty cool addition.  I immediately started thinking interesting ways to harness the introspection features for debugging.  

Cheers,

Ed

@Dave,

Thanks First

Let me say “Thank You.”   I should have started off with that previously.  I’m afraid I’m not good at setting the proper tone in replies and may have come across as dismissive when I was just not doing a good job of describing my usages.

So, to be clear, I am very thankful for any and all responses.  I very much appreciate the time it takes to answer.

Contrived my example was…

You are correct.  The example I gave was very contrived.  You are also correct when you say " I understand it’s not always easy to construct a “simple” test to convey intended usage. "

My examples was designed to run in a single file to demonstrate the sequence of events, rather than the problem.

In my real apps and games I do the following:

  • Create objects in different files.

  • Add these objects to the same hierarchy of groups. (I use groups for display layering and all layers have a common parent group for easy destruction.)

  • Instead of maintaining tables of specific objects types for task purposes, I attach Runtime listeners to them and dispatch custom events (see example below).

  • Later, I will destroy the parent group and simultaneously, destroy all of the objects in the group.  At this point, my problem occurs with any and all Runtime listeners (including ‘enterFrame’).  They basically dangle for one frame (or more?) till the object is cleaned up.  

Arbitrary Example of Custom Runtime Events

Module boomer.lua:

local public = {} local function makeBoomer( group ) local obj = display.newCircle( group, 100, 100, 10 ) obj.onBoom = function( self, event ) transition.to( self, { xScale = 10, yScale = 10, alpha = 0, onComplete = display.remove } ) end Runtime:addEventListener( "onBoom", obj ) end public.create = makeBoomer return public

Elsewhere…

local boomer = require "boomer" local tmp = display.newGroup boomer.create( tmp ) display.remove( tmp ) tmp = nil timer.performWithDelay( 1, function() Runtime:dispatchEvent( "onBoom" ) end )

In the above example, the code does not function as I would wish it to.  Instead, the ‘onBoom’ event still runs.

Solutions

Some solutions are:

  • Create a custom clean up event and call it. - i.e. Use the same event system and have the ‘cleanup’ function remove all listeners from the object and then destroy it.
    • Good because - This would solve the problem and ensure no leaks and no event firing after destruction.
    • Bad because - Complicated to code and maintain and will fail the first time something gets destroyed before the event is first fired.  This is basically the same as tracking objects manually and cleaning them up in terms of complexity and ugliness.
  • Similarly (to above), use the ‘finalize’ event which is now always called regardless of how an object is destroyed (kudos to Corona).
    • Good because - This is clean and part of the standard API.  It is also guaranteed, thus solving one issue with the prior solution.
    • Bad because - This is still a lot of work to maintain for a lazy coder like me.  (Still, I did jump on the bandwagon and ask this feature be fixed, so “Thanks again to the Corona staff!”)
  • My flag method - This fits easily into my own method of coding and solves the problem as long as I check for the flag, which is a one-liner in my code:
    • if( autoIgnore( “enterFrame”, self ) ) then return end
    • The above line is a helper function of mine that does this:
      • Checks if the object was marked destroyed, if not returns false, if so continues
      • Removes the named event listener from the objects
      • Returns true
    • I simply put the above line in my listener definitions at the top and they automatically get cleaned and never run for destroyed objects.   Easy-peasy.

Here is the above example with my custom Runtime shorthand functions:

Shorthand helpers:

\_G.listen = function( name, listener ) Runtime:addEventListener( name, listener ) end \_G.ignore = function( name, listener ) Runtime:removeEventListener( name, listener ) end \_G.autoIgnore = function( name, obj ) if( obj.\_\_destroyed or obj.removeSelf == nil ) then ignore( name, obj ) obj[name] = nil return true end return false end \_G.post = function( name, params, debuglvl ) local params = params or {} local event = { name = name } for k,v in pairs( params ) do event[k] = v end if( not event.time ) then event.time = system.getTimer() end Runtime:dispatchEvent( event ) end

boomer.lua (2.0)

local public = {} local function makeBoomer( group ) local obj = display.newCircle( group, 100, 100, 10 ) obj.onBoom = function( self, event ) if( autoIgnore( "onBoom", self) ) then return end transition.to( self, { xScale = 10, yScale = 10, alpha = 0, onComplete = display.remove } ) end listen( "onBoom", obj ) end public.create = makeBoomer return public

local boomer = require "boomer" local tmp = display.newGroup boomer.create( tmp ) display.remove( tmp ) tmp = nil timer.performWithDelay( 1, function() post( "onBoom" ) end )

This time, all is well.

>> In the above example, the code does not function as I would wish it to.  Instead, the ‘onBoom’ event still runs.

Tho it does behave exactly as per docs (and exactly as i would expect), fe (and elsewhere):  http://docs.coronalabs.com/daily/guide/events/detectEvents/index.html#removing-event-listeners

So, it would seem that the crux of your matter is that you’re hoping display.remove (in your transition’s onComplete) will also remove stuff from Runtime.  There are good reasons why runtime events persist, so I wouldn’t expect that behavior to change (at least not without angering developers by breaking existing code)

>>Some solutions are:

Somewhere amidst the first and second is an even simpler approach, that doesn’t further compound the issue by using events or finalize.  Why not just create your own more elaborate removal function and just call it _ directly _ onComplete, replacing the call to display.remove, but wrapping that functionality too?  roughly fe:

transition.to( …whatever…  onComplete=myCustomerRemover})

function myCustomRemover(obj)

  obj._functionListeners = nil – this is hackery, but unfortunately there’s no formal: obj:removeEventListener("*")

  obj._tableListeners = nil – ditto, or you could instead pairs() through the obj looking for “on*()” and remove those by name?

  – maybe even:  transition.cancel(obj) and whatever ELSE might need cleanup

  display.remove(obj) – finally, duplicate original functionality

end

…and done!   :slight_smile:

[Edit:  nope, not quite done.  :D  I hadn’t actually tried that “hackery” in a while, but seems to no longer work – as such hackery is wont to do.  (Maybe someone else knows the current version of that hack, if it still exists in some form?)  At any rate, ignore as written, tho you could still perhaps do a pairs() search as in comment.  I still think a direct call to your own removal function would be the most straightforward approach.]