Seeking Expert Assistance - 'onRemove' Event

Hello experts and curious readers. 

I am trying to find a way to force all display objects to automatically post an event just prior to removal. 

My purpose in doing this, is to enable easy destruction and cleanup in cases where it is essential that certain actions occur just prior to the removal of an object.

Clarification on the term ‘Removal’

For me, the words remove and destroy when referring to a display object are synonymous.  I am referring to the point in time when Corona removes that object from the processing and rendering pool and hands over management of the left-over stuff to Lua.

In Corona SDK, object destruction (as I understand it) can be broken down into two major phases:

  1. Remove Corona bits and stop processing and rendering the object.  Leaves a Lua Table stub behind.  ( I only care about this part. )
  2. Remove the Lua table stub and schedule it for garbage collection at a future date.  At this point the object is completely gone.

Assuming I have this code, 

local myGroup = display.newGroup() local myCircle = display.newCircle( myGroup, 240, 160, 30 ) myCircle.onRemove = function( self, event ) -- Do something here end

the object ‘myCircle’ can be removed in each of these ways:

-- 1. removeSelf() on the object. -- myCircle:removeSelf() -- 2. display.remove() the object. -- display.remove( myCircle ) -- 3. removeSelf() on group containing the object. -- myGroup:removeSelf() -- 4. display.remove() group containing the object. -- display.remove( myGroup ) -- 5/6. - Storyboard and Composer removals. -- -- For storyboard or composer, if the group 'myGroup' (which contains 'myCircle') -- was added to the scene's group, 'myGroup' and 'myCircle' will be removed -- when the scene is destroyed. -- 7. - App/Game destruction. -- -- When the app is killed/stopped/destroyed the group and object will also -- be destroyed

The Behavior I’m Looking For (Trying to Create)

In each of the above cases, I would like to find a way to have the method ‘onRemove()’ get called automatically just prior to the removal.  The key here is that I’d like this to be automatic.

I am considering filing a feature request to add new Corona event like ‘touch’, ‘collision’, etc that will fire just prior to destruction.  

However before I do that, I’d like to use your brain and see if anyone knows of a way to do this now.  My thought is that there may be a way to make this happen with Lua metatable programming.  Unfortunately, all my efforts to date have failed.

So, after that long soliloquy, if you have any suggestions, ideas, or questions please post them below.

Thanks!

Ed M. 

Iirc there was a “finalize” method added to display objects that looks like an ideal fit for this.

With the caveat being that it gets fired *after* removal.

http://docs.coronalabs.com/api/event/finalize/index.html

fwiw (ie, no claim of “expert”), just because it’s an interesting puzzler: from a Lua perspective this will be problematic without support from Corona (assuming your ‘before’ spec eliminates ‘finalize’ from consideration). you’re likely going to have to patchwork fix it, no single-source solution suggests itself (to me anyway).

metamethod approach: __gc obviously occurs far too late to be of ANY use; that leaves __call, which might be able to intercept remove()/removeSelf(), except that iirc, few of Corona’s userdata objects will gracefully accept reassigning their metatable directly. (so you’d likely have to “double-wrap” them) so i probably wouldn’t bother with that approach

what could work: a bunch of patchwork overrides. consider this, as the first of many (though this alone is a pretty big chunk of it – all the display.remove() calls)

local \_overrides = {} -- save stuff here \_overrides.remove = display.remove display.remove = function(obj) if (obj) then local event = { name="remove" } obj:dispatchEvent(event) -- or just: if (type(obj.remove)=="function") then obj:remove() end -- if you'd rather not have to explicitly add listeners end \_overrides.remove(obj) end do local myGroup = display.newGroup() local myCircle = display.newCircle( myGroup, 240, 160, 30 ) function myCircle:remove(event) print("test 1, i'm being removed") end myCircle:addEventListener("remove",myCircle) display.remove(myCircle) end

then what? next biggest bang-for-buck would be to override display.newGroup(), and “decorate” its remove() with an overridden version that does similar to above, dispatching to child before actual removal, return custom-decorated object. (this then should cover all the group.remove() calls, including scene’s)

you’ll have to watch out for “holes” tho: assert that groups MUST listen for onRemove if they contain any children that do, that sort of thing, otherwise display.remove(nonListeningGroupWithListeningChildren) wouldn’t fire the children listeners.

then? much smaller returns, but same for everything else: display.newRect, display.newImage, display.newImageRect, etc. override/decorate all the individual removeSelf()'s. lots and lots of argument parsing here to make sure YOUR api wrapper functionally matches the original, PITA. :smiley:

i can’t immediately see any more “automatic” way to do it - props to anyone that does. :slight_smile:

**FWIW** hth

Gremlin is right.  You want to use the finalize handler.  Here’s an example I used in the tutorial on text input where I used an enterFrame listener to keep the native field in sync with it’s display object background:

    function field:finalize( event )         print("Finalizing field")         Runtime:removeEventListener( "enterFrame", syncFields )         event.target.textField:removeSelf()     end       field:addEventListener( "finalize" )

Note: This is the composite of good answers above and not the best answer in and of itself.  I just wanted to mark this closed/solved.

I tested out the ideas presented (and variants) and came up with this:   

local testNum = 1 -- 1 .. 6 local function someWork( self, event )     print(self.name)     for k,v in pairs(event) do         print(k,v)     end end local function onFinalize( self, event )     self:someWork( event ) end local myGroup = display.newGroup() local myCircle = display.newCircle( myGroup, 240, 160, 30 ) myGroup.name = "myGroup" myGroup.finalize = onFinalize myGroup.someWork = someWork myGroup:addEventListener( "finalize" ) myCircle.name = "myCircle" myCircle.finalize = onFinalize myCircle.someWork = someWork myCircle:addEventListener( "finalize" ) print("\nTest:" .. testNum .. "\n") if( testNum == 1 ) then -- PASS     myCircle:removeSelf() elseif( testNum == 2 ) then -- PASS     display.remove( myCircle ) elseif( testNum == 3 ) then -- FAIL (only calls finalize for 'myGroup')     myGroup:removeSelf() elseif( testNum == 4 ) then -- FAIL (only calls finalize for 'myGroup')     display.remove( myGroup ) elseif( testNum == 5 ) then -- PASS      -- Override display.remove     display.\_\_remove = display.remove     display.remove = function ( obj )  if( obj == nil) then return end         if( obj.numChildren == nil ) then             display.\_\_remove( obj )         else             local child             for i = 1, obj.numChildren do                 child = obj[i]                 display.remove( child )             end             display.\_\_remove( obj )         end     end     display.remove( myGroup ) elseif( testNum == 6 ) then -- PASS      -- Override display.remove     display.\_\_remove = display.remove     display.remove = function ( obj )  if( obj == nil) then return end         if( obj.numChildren == nil ) then             display.\_\_remove( obj )         else             local child             for i = 1, obj.numChildren do                 child = obj[i]                 display.remove( child )             end             display.\_\_remove( obj )         end     end     local myGroup2 = display.newGroup()     myGroup2:insert(myGroup)     myGroup2.name = "myGroup2"     myGroup2.finalize = onFinalize     myGroup2.someWork = someWork     myGroup2:addEventListener( "finalize" )     display.remove( myGroup2 ) end

Summary

  • The ‘finalize’ event is OK as long as you manually remove an object or a group.
    • finalize() gets executed for objects or groups that are manually removed.
  • The ‘finalize’ event won’t do what I need if you remove a group with objects inside it that have finalize methods.
    • ‘finalize’ is not automatically called for the children.
  • ‘finalize’ fails to be useful for children of groups owned by ‘composer’ or ‘storyboard’.
    • ‘finalize’ is not automatically called for the children.
  • A little trickery can be used to extend display.remove() to enable finalize calls for all children.
    • See examples 5 and 6

Thanks all!

PS - I’d love to see ‘finalize’ automatically executed for all scenarios w/o trickery, but this will do.

Iirc there was a “finalize” method added to display objects that looks like an ideal fit for this.

With the caveat being that it gets fired *after* removal.

http://docs.coronalabs.com/api/event/finalize/index.html

fwiw (ie, no claim of “expert”), just because it’s an interesting puzzler: from a Lua perspective this will be problematic without support from Corona (assuming your ‘before’ spec eliminates ‘finalize’ from consideration). you’re likely going to have to patchwork fix it, no single-source solution suggests itself (to me anyway).

metamethod approach: __gc obviously occurs far too late to be of ANY use; that leaves __call, which might be able to intercept remove()/removeSelf(), except that iirc, few of Corona’s userdata objects will gracefully accept reassigning their metatable directly. (so you’d likely have to “double-wrap” them) so i probably wouldn’t bother with that approach

what could work: a bunch of patchwork overrides. consider this, as the first of many (though this alone is a pretty big chunk of it – all the display.remove() calls)

local \_overrides = {} -- save stuff here \_overrides.remove = display.remove display.remove = function(obj) if (obj) then local event = { name="remove" } obj:dispatchEvent(event) -- or just: if (type(obj.remove)=="function") then obj:remove() end -- if you'd rather not have to explicitly add listeners end \_overrides.remove(obj) end do local myGroup = display.newGroup() local myCircle = display.newCircle( myGroup, 240, 160, 30 ) function myCircle:remove(event) print("test 1, i'm being removed") end myCircle:addEventListener("remove",myCircle) display.remove(myCircle) end

then what? next biggest bang-for-buck would be to override display.newGroup(), and “decorate” its remove() with an overridden version that does similar to above, dispatching to child before actual removal, return custom-decorated object. (this then should cover all the group.remove() calls, including scene’s)

you’ll have to watch out for “holes” tho: assert that groups MUST listen for onRemove if they contain any children that do, that sort of thing, otherwise display.remove(nonListeningGroupWithListeningChildren) wouldn’t fire the children listeners.

then? much smaller returns, but same for everything else: display.newRect, display.newImage, display.newImageRect, etc. override/decorate all the individual removeSelf()'s. lots and lots of argument parsing here to make sure YOUR api wrapper functionally matches the original, PITA. :smiley:

i can’t immediately see any more “automatic” way to do it - props to anyone that does. :slight_smile:

**FWIW** hth

Gremlin is right.  You want to use the finalize handler.  Here’s an example I used in the tutorial on text input where I used an enterFrame listener to keep the native field in sync with it’s display object background:

    function field:finalize( event )         print("Finalizing field")         Runtime:removeEventListener( "enterFrame", syncFields )         event.target.textField:removeSelf()     end       field:addEventListener( "finalize" )

Note: This is the composite of good answers above and not the best answer in and of itself.  I just wanted to mark this closed/solved.

I tested out the ideas presented (and variants) and came up with this:   

local testNum = 1 -- 1 .. 6 local function someWork( self, event )     print(self.name)     for k,v in pairs(event) do         print(k,v)     end end local function onFinalize( self, event )     self:someWork( event ) end local myGroup = display.newGroup() local myCircle = display.newCircle( myGroup, 240, 160, 30 ) myGroup.name = "myGroup" myGroup.finalize = onFinalize myGroup.someWork = someWork myGroup:addEventListener( "finalize" ) myCircle.name = "myCircle" myCircle.finalize = onFinalize myCircle.someWork = someWork myCircle:addEventListener( "finalize" ) print("\nTest:" .. testNum .. "\n") if( testNum == 1 ) then -- PASS     myCircle:removeSelf() elseif( testNum == 2 ) then -- PASS     display.remove( myCircle ) elseif( testNum == 3 ) then -- FAIL (only calls finalize for 'myGroup')     myGroup:removeSelf() elseif( testNum == 4 ) then -- FAIL (only calls finalize for 'myGroup')     display.remove( myGroup ) elseif( testNum == 5 ) then -- PASS      -- Override display.remove     display.\_\_remove = display.remove     display.remove = function ( obj )  if( obj == nil) then return end         if( obj.numChildren == nil ) then             display.\_\_remove( obj )         else             local child             for i = 1, obj.numChildren do                 child = obj[i]                 display.remove( child )             end             display.\_\_remove( obj )         end     end     display.remove( myGroup ) elseif( testNum == 6 ) then -- PASS      -- Override display.remove     display.\_\_remove = display.remove     display.remove = function ( obj )  if( obj == nil) then return end         if( obj.numChildren == nil ) then             display.\_\_remove( obj )         else             local child             for i = 1, obj.numChildren do                 child = obj[i]                 display.remove( child )             end             display.\_\_remove( obj )         end     end     local myGroup2 = display.newGroup()     myGroup2:insert(myGroup)     myGroup2.name = "myGroup2"     myGroup2.finalize = onFinalize     myGroup2.someWork = someWork     myGroup2:addEventListener( "finalize" )     display.remove( myGroup2 ) end

Summary

  • The ‘finalize’ event is OK as long as you manually remove an object or a group.
    • finalize() gets executed for objects or groups that are manually removed.
  • The ‘finalize’ event won’t do what I need if you remove a group with objects inside it that have finalize methods.
    • ‘finalize’ is not automatically called for the children.
  • ‘finalize’ fails to be useful for children of groups owned by ‘composer’ or ‘storyboard’.
    • ‘finalize’ is not automatically called for the children.
  • A little trickery can be used to extend display.remove() to enable finalize calls for all children.
    • See examples 5 and 6

Thanks all!

PS - I’d love to see ‘finalize’ automatically executed for all scenarios w/o trickery, but this will do.