Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

Remove all objects
Started by dellagd Mar 05 2011 08:23 AM

- - - - -
59 replies to this topic
[TOPIC CONTROLS]
Page 1 of 3 1 2 3
This topic has been archived. This means that you cannot reply to this topic.
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

dellagd

[GLOBAL: userInfoPane.html]
dellagd
  • Observer

  • 22 posts
  • Guests

Hello-

I have code in my app that spawns a piece multiple times. Currently, I have that every time a user clicks on a button, this is called

T_supporter = display.newImage("T_supporter.png")

I was trying to set up a way to remove them all by calling this

T_supporter:removeSelf()

but it would only remove the object that was created on the first spawn and none after that.

Can you please show me where I went wrong?
uid: 35210 topic_id: 7489 reply_id: 307489


[TOPIC: post.html]
#2

Skatan

[GLOBAL: userInfoPane.html]
Skatan
  • Contributor

  • 547 posts
  • Corona SDK

Try creating a group, then add every object you spawn into that group. When you want to remove everything just remove the group
uid: 14018 topic_id: 7489 reply_id: 26507


[TOPIC: post.html]
#3

dellagd

[GLOBAL: userInfoPane.html]
dellagd
  • Observer

  • 22 posts
  • Guests

I have tried creating a group and every time a spawn happens, I call this

blocksGroup:insert(T_supporter [false])

then, when all is said and done, I call this to get rid of all of them

blocksGroup:removeSelf()

What am I doing wrong?
uid: 35210 topic_id: 7489 reply_id: 26525


[TOPIC: post.html]
#4

Skatan

[GLOBAL: userInfoPane.html]
Skatan
  • Contributor

  • 547 posts
  • Corona SDK

What's that false thing you got there?
uid: 14018 topic_id: 7489 reply_id: 26535


[TOPIC: post.html]
#5

jonbeebe

[GLOBAL: userInfoPane.html]
jonbeebe
  • Contributor

  • 511 posts
  • Corona SDK

Unfortunately, you can't just remove the group and expect all the objects within it to be removed as well.

Here's a function you can use (taken from Ricardo Rauber's director class):

[blockcode]
local function cleanGroups ( curGroup, level )
if curGroup.numChildren then
while curGroup.numChildren > 0 do
cleanGroups ( curGroup[curGroup.numChildren], level+1 )
end
if level > 0 then
curGroup:removeSelf()
end
else
curGroup:removeSelf()
curGroup = nil
return
end
end
[/blockcode]

Add that function to the top of your lua file, and then you'd call:

cleanGroups( blockGroups, 0 )


For extra assurance, you can even do a:

blockGroups = nil


After you call cleanGroups() just to be on the safe side. What that will do is first remove all objects/groups within blocksGroup, and then it'll remove blocksGroup itself.

Hope that helps!
uid: 7849 topic_id: 7489 reply_id: 26540


[TOPIC: post.html]
#6

FrankS

[GLOBAL: userInfoPane.html]
FrankS
  • Contributor

  • 219 posts
  • Corona SDK

Not sure if I understand how this function works...

As I understand it, it only will recursively removeSelf any display groups and not any display objects that are inside of a group as for a display object o, o.numChildren returns nil.

Furthermore, the reason for the "level" parameter seems to be to do a "curGroup = nil" when you're down in a recursion. But as "curGroup" is a function parameter, it will cease to exist outside of the function scope and nil'ing the reference should have no effect.

Rewriting it to truly removeSelf all display object and groups, and not to bother to nil any parameters that will "self-destruct" outside the function scope, the following could be an alternative:

--- Function will bottom-up recursively removeSelf all display objects and groups that may be part of objectOrGroup, before removeSelf'ing itself.--@param objectOrGroup a display object or group to be removeSelf'ed with all its possible members.local function cleanGroups ( objectOrGroup )	if objectOrGroup.numChildren then		-- we have a group, so first clean that out		while objectOrGroup.numChildren > 0 do			-- clean out the last member of the group (work from the top down!)			cleanGroups ( objectOrGroup[objectOrGroup.numChildren])		end	end	-- we have either an empty group or a normal display object - remove it	objectOrGroup:removeSelf()	return    endend


Please let me know if I misunderstood the working of this cleanGroups function.

I do agree that it is good to nil the reference to the objectOrGroup in the calling program, as there will be a table-skeleton or display-object-orphan left after the removeSelf, and holding on to references will impede the garbage collection of this orphan (unlike the function parameter). So maybe do a standard:
blockGroups = cleanGroups( blockGroups )


-FrankS

uid: 8093 topic_id: 7489 reply_id: 26557


[TOPIC: post.html]
#7

jonbeebe

[GLOBAL: userInfoPane.html]
jonbeebe
  • Contributor

  • 511 posts
  • Corona SDK

@FrankS: I can't see what you did besides rename curGroup to objectOrGroup and remove the level parameter...

As it stands, curGroup and objectOrGroup works the same, regardless of what it's named. I have the function working in multiple apps and it does in fact remove all display objects and groups with the top-level group that you specify.

I do like your suggestion to simply assign the group to the function, so that it nils out on its own, but it's all a matter of personal preference. Do it that way or just nil out after. What's important is that it's done :-)
uid: 7849 topic_id: 7489 reply_id: 26560


[TOPIC: post.html]
#8

FrankS

[GLOBAL: userInfoPane.html]
FrankS
  • Contributor

  • 219 posts
  • Corona SDK

Sorry Jon, but I did move the "removeSelf" outside of the "if curGroup.numChildren" test, which makes all the difference for normal display objects.

I tried to annotate the original cleanGroups() to show the issue:

local function cleanGroups ( curGroup, level )    if curGroup.numChildren then    -- you only get here if curGroup.numChildren ~= nil    -- local o = display.newCircle(100,100,10)    -- print(o.numChildren)   => nil    -- so display objects do not get here, only groups         while curGroup.numChildren > 0 do                cleanGroups ( curGroup[curGroup.numChildren], level+1 )        end        if level > 0 then                curGroup:removeSelf()        end        else        curGroup:removeSelf()                curGroup = nil        return    end  -- end of "if curGroup.numChildren"    -- display objects fall thru here and do not get removeSelf'edend


Now... the fact that it seems to work for you could imply that either you didn't notice it or that display objects get automagically removed... or that it's very late here and I suffered from a major brain fart, but then I'd like to know so please bear with me ;-)

-Frank.

uid: 8093 topic_id: 7489 reply_id: 26568


[TOPIC: post.html]
#9

jonbeebe

[GLOBAL: userInfoPane.html]
jonbeebe
  • Contributor

  • 511 posts
  • Corona SDK

Ah great point FrankS, will definitely take your function and do some testing...

Thanks!
uid: 7849 topic_id: 7489 reply_id: 26570


[TOPIC: post.html]
#10

jonbeebe

[GLOBAL: userInfoPane.html]
jonbeebe
  • Contributor

  • 511 posts
  • Corona SDK

@FrankS: Before, it seemed as though display objects were in fact being removed, but I suppose there was some sort of reference left behind (and thus causing memory leaks, ouch!). Your updated cleanGroups() function seems to take care of that problem.

So thank you very much for that. The Director Class definitely needs the cleanGroups() function updated using this one. I always had a little instability problems while using the Director Class, and this might just be it! Thanks again...

For those who missed it, here's the "good" cleanGroups function:

[blockcode]
local function cleanGroups ( objectOrGroup )
if objectOrGroup.numChildren then
-- we have a group, so first clean that out
while objectOrGroup.numChildren > 0 do
-- clean out the last member of the group (work from the top down!)
cleanGroups ( objectOrGroup[objectOrGroup.numChildren])
end
end

-- we have either an empty group or a normal display object - remove it
objectOrGroup:removeSelf()

return
end
[/blockcode]

Thanks again @FrankS!
uid: 7849 topic_id: 7489 reply_id: 26574


[TOPIC: post.html]
#11

FrankS

[GLOBAL: userInfoPane.html]
FrankS
  • Contributor

  • 219 posts
  • Corona SDK

Glad to hear that it makes a difference.

I find that all this display-object removeSelf stuff is difficult to get right as first you have to get a hold of each and every one of them, and second any additional reference to that removeSelf'ed display-object will keep the skeleton/orphan from being garbage collected.

Wonder if the Corona-runtime couldn't help us more with at least removeSelf'ing some of these display objects automatically when it's obvious that there is no other reference then the one inside of a display-object-group (?) or if there is simply no other reference. In the latter case, the rule would be that if the garbage collector would be able to remove it, then Corona should be able to removeSelf it...

Good night, FrankS.
uid: 8093 topic_id: 7489 reply_id: 26577


[TOPIC: post.html]
#12

FrankS

[GLOBAL: userInfoPane.html]
FrankS
  • Contributor

  • 219 posts
  • Corona SDK

Just one more issue before I forget: the current "cleanGroups ( objectOrGroup )" still chokes on objects that are not display objects or display-objects that have been removeSelf'ed already.

I use the following function to test whether an object is a valid display-object or group:

local coronaMetaTable = getmetatable(display.getCurrentStage())--- Returns whether aDisplayObject is a Corona display object.-- note that all Corona types seem to share the same metatable, which is used for the test.--@param aDisplayObject table - possible display object.--@return boolean - true if object is a display objectisDisplayObject = function(aDisplayObject)	return (type(aDisplayObject) == "table" and getmetatable(aDisplayObject) == coronaMetaTable)end


So by adding that test to cleanGroups(), you would make it more robust:
local function cleanGroups ( objectOrGroup )    if(not isDisplayObject(objectOrGroup)) then return end    if objectOrGroup.numChildren then                -- we have a group, so first clean that out                while objectOrGroup.numChildren > 0 do                        -- clean out the last member of the group (work from the top down!)                        cleanGroups ( objectOrGroup[objectOrGroup.numChildren])                end    end        -- we have either an empty group or a normal display object - remove it    objectOrGroup:removeSelf()        returnend


Now I'm really going to bed ;-)
-FrankS.
uid: 8093 topic_id: 7489 reply_id: 26578


[TOPIC: post.html]
#13

dellagd

[GLOBAL: userInfoPane.html]
dellagd
  • Observer

  • 22 posts
  • Guests

Well, I have tried to implement that code, but it gives me a stack overflow error
uid: 35210 topic_id: 7489 reply_id: 26592


[TOPIC: post.html]
#14

FrankS

[GLOBAL: userInfoPane.html]
FrankS
  • Contributor

  • 219 posts
  • Corona SDK

It's difficult to understand what caused the stack overflow without more details...

The attached snippet works for me as expected (save as main.lua and run):

local coronaMetaTable = getmetatable(display.getCurrentStage()) --- Returns whether aDisplayObject is a Corona display object.-- note that all Corona types seem to share the same metatable, which is used for the test.--@param aDisplayObject table - possible display object.--@return boolean - true if object is a display objectlocal isDisplayObject = function(aDisplayObject)        return (type(aDisplayObject) == "table" and getmetatable(aDisplayObject) == coronaMetaTable)endlocal function cleanGroups ( objectOrGroup )    if(not isDisplayObject(objectOrGroup)) then return end    if objectOrGroup.numChildren then                -- we have a group, so first clean that out                while objectOrGroup.numChildren > 0 do                        -- clean out the last member of the group (work from the top down!)                        cleanGroups ( objectOrGroup[objectOrGroup.numChildren])                end    end        -- we have either an empty group or a normal display object - remove it    objectOrGroup:removeSelf()        returnendlocal o = display.newCircle(100,100,10)local g = display.newGroup()for i = 1,100 do 	local lg = display.newGroup()	lg:insert(display.newCircle(math.random(200),math.random(300),math.random(100)))	g:insert(lg) end	print("before info")print("o.numChildren",o and o.name, o and o.numChildren, isDisplayObject(o))print("g.numChildren",g and g.name, g and g.numChildren, isDisplayObject(g))local info = function()	print("before cleanGroups")	print("o.numChildren",o and o.name, o and o.numChildren, isDisplayObject(o))	print("g.numChildren",g and g.name, g and g.numChildren, isDisplayObject(g))	cleanGroups ( o )	cleanGroups ( g )		print("after cleanGroups")	print("o.numChildren",o and o.name, o and o.numChildren, isDisplayObject(o))	print("g.numChildren",g and g.name, g and g.numChildren, isDisplayObject(g))endtimer.performWithDelay(3000, info, 1)


-FrankS.
uid: 8093 topic_id: 7489 reply_id: 26613


[TOPIC: post.html]
#15

jonbeebe

[GLOBAL: userInfoPane.html]
jonbeebe
  • Contributor

  • 511 posts
  • Corona SDK

@FrankS: Awesome, awesome stuff here.

I implemented the isDisplayObject and your cleanGroups function and it works flawlessly in my project. Thanks a lot for this.

If you don't mind, I want to make a blog post about this. Don't worry, will definitely give you all the credit as my source of this information, I just want it to be stored somewhere out there in the event this forum goes down (or this thread is buried so far that nobody will ever find it).

Thanks again FrankS, it was a big help.

@dellagd: You might try looking at other areas in your code, I don't think that it's directly having to do with your cleanGroups, it might just be something else in your code indirectly conflicting with it.
uid: 7849 topic_id: 7489 reply_id: 26616


[TOPIC: post.html]
#16

dellagd

[GLOBAL: userInfoPane.html]
dellagd
  • Observer

  • 22 posts
  • Guests

Great News, got it all working

Just one thing, not really related to this whole ordeal very much, when I insert the objects into the group, they are sent behind my background image.
Can someone help me with this?
uid: 35210 topic_id: 7489 reply_id: 26632


[TOPIC: post.html]
#17

FrankS

[GLOBAL: userInfoPane.html]
FrankS
  • Contributor

  • 219 posts
  • Corona SDK

Great to hear all that good news - I enjoy your blog - hopefully useful for others also - regards, FrankS.
uid: 8093 topic_id: 7489 reply_id: 26636


[TOPIC: post.html]
#18

jonbeebe

[GLOBAL: userInfoPane.html]
jonbeebe
  • Contributor

  • 511 posts
  • Corona SDK

@dellagd: You can either do a myGroup:toFront() or... myBackground:toBack()
uid: 7849 topic_id: 7489 reply_id: 26679


[TOPIC: post.html]
#19

p120ph37

[GLOBAL: userInfoPane.html]
p120ph37
  • Enthusiast

  • 46 posts
  • Corona SDK

My solution to this issue was essentially the inverse of the recursive one explained above. I wrapped the display.newGroup factory and used that to extend all subsequently created group objects with an enhanced removeSelf method which removes not only the group object itself but also its immediate children (which will in turn remove their children if they are also extended group objects). Here is an example showing how this effectively eliminates the memory leaks. What I particularly like about this solution is that it can be placed in a module of its own and simply required in, and with no other code changes your group-removal memory leaks just go away.

-- Fix memory leak in group:removeSelf() ...
--   ... and proxy group:remove( o ) to o:removeSelf()
local oldNewGroup = display.newGroup
display.newGroup = function( ... )
  local group = oldNewGroup( ... )
  local oldRemoveSelf = group.removeSelf
  group.removeSelf = function( self )
    for i = self.numChildren, 1, -1 do
      self[i]:removeSelf()
    end
    oldRemoveSelf( self )
  end
  group.remove = function( self, o )
    if type( o ) == 'number' then
      self[o]:removeSelf()
    else
      o:removeSelf()
    end
  end
  return group
end
-- patch stage object to proxy stage:remove( o ) to o:removeSelf()
display.getCurrentStage().remove = function( self, o )
  if type( o ) == 'number' then
    self[o]:removeSelf()
  else
    o:removeSelf()
  end
end

-----

Runtime:addEventListener('enterFrame', function()
  local g0 = display.newGroup()
  local g1 = display.newGroup(); g0:insert(g1)
  local g2 = display.newGroup(); g0:insert(g2)
  local g3 = display.newGroup(); g0:insert(g3)
  local r1 = display.newRect( 10, 10, 25, 25 ); g1:insert(r1)
  local r2 = display.newRect( 20, 20, 25, 25 ); g1:insert(r2)
  local r3 = display.newRect( 30, 30, 25, 25 ); g1:insert(r3)
  local r4 = display.newRect( 40, 40, 25, 25 ); g2:insert(r4)
  local r5 = display.newRect( 50, 50, 25, 25 ); g2:insert(r5)
  local r6 = display.newRect( 60, 60, 25, 25 ); g2:insert(r6)
  local r7 = display.newRect( 70, 70, 25, 25 ); g3:insert(r7)
  local r8 = display.newRect( 80, 80, 25, 25 ); g3:insert(r8)
  local r9 = display.newRect( 90, 90, 25, 25 ); g3:insert(r9)
  -- clear all
  local stage = display.getCurrentStage()
  for i = stage.numChildren, 1, -1 do
    stage:remove(i)
  end
  -- watch for leaks
  collectgarbage()
  print(collectgarbage('count'))
end)
uid: 32962 topic_id: 7489 reply_id: 26723


[TOPIC: post.html]
#20

jonbeebe

[GLOBAL: userInfoPane.html]
jonbeebe
  • Contributor

  • 511 posts
  • Corona SDK

@p120ph37: Thank you so much for your contributions as well! Surely that will come in very handy. I've already included it in my project to see how it works out :-)
uid: 7849 topic_id: 7489 reply_id: 26737


[TOPIC: post.html]
#21

jonbeebe

[GLOBAL: userInfoPane.html]
jonbeebe
  • Contributor

  • 511 posts
  • Corona SDK

@p120ph37: Just for clarification, your new group:removeSelf() method should replace a need for a cleanGroups() function, and be able to effectively "clean out" a group just by calling:

myGroup:removeSelf()

Is that correct?

Thanks again!
uid: 7849 topic_id: 7489 reply_id: 26738


[TOPIC: post.html]
#22

dellagd

[GLOBAL: userInfoPane.html]
dellagd
  • Observer

  • 22 posts
  • Guests

Thanks, its all good now.
uid: 35210 topic_id: 7489 reply_id: 26756


[TOPIC: post.html]
#23

p120ph37

[GLOBAL: userInfoPane.html]
p120ph37
  • Enthusiast

  • 46 posts
  • Corona SDK

@jonbeebe: yes, that's the idea exactly.
uid: 32962 topic_id: 7489 reply_id: 26770


[TOPIC: post.html]
#24

FrankS

[GLOBAL: userInfoPane.html]
FrankS
  • Contributor

  • 219 posts
  • Corona SDK

The solution where you substitute the global display.newGroup() with the patched-one may not work always.

In many libraries you see the pattern:

local newGroup = display.newGroup


(I just used newGroup here to make the point)

Some even recommend this pattern as a best practice because it forces you to localize all the symbols that you use in your module. Leaking globals that pollute and overwrite the Lua namespace is a nasty problem.

If you use this localization, however, then it depends on when you do the patching and when do you require the module.

For example, take the following file "monkey.lua":
local newGroup = display.newGroup
print("monkey.newGroup begin:", newGroup)

local monkey = {}

monkey.newGroup = function(...)
	print("monkey.newGroup:", newGroup)
	return newGroup(...)
end

return monkey


and add a require("monkey") before the newGroup-patch, and substitute the first display.newGroup in the event handler with monkey.newGroup:

local monkey = require("monkey")

local oldNewGroup = display.newGroup
print("oldNewGroup:", oldNewGroup)
display.newGroup = function( ... )
  local group = oldNewGroup( ... )
  local oldRemoveSelf = group.removeSelf
  group.removeSelf = function( self )
    for i = self.numChildren, 1, -1 do
      self[i]:removeSelf()
    end
    if(self.name=="ui")then print("newRemoveSelf", self) end
    oldRemoveSelf( self )
  end
  group.remove = function( self, o )
    if type( o ) == 'number' then
      self[o]:removeSelf()
    else
      o:removeSelf()
    end
  end
  return group
end
--]]

-- patch stage object to proxy stage:remove( o ) to o:removeSelf()
display.getCurrentStage().remove = function( self, o )
  if type( o ) == 'number' then
    self[o]:removeSelf()
  else
    o:removeSelf()
  end
end

print("new newGroup:", display.newGroup)

local NamedObjects = require("NamedObjects")
-----
 
Runtime:addEventListener('enterFrame', function()
	print("new newGroup:", display.newGroup)
	NamedObjects.newGroupRef()
  local g0 = monkey.newGroup()
  local g1 = display.newGroup(); g0:insert(g1)
  local g2 = display.newGroup(); g0:insert(g2)
  local g3 = display.newGroup(); g0:insert(g3)
  local r1 = display.newRect( 10, 10, 25, 25 ); g1:insert(r1)
  local r2 = display.newRect( 20, 20, 25, 25 ); g1:insert(r2)
  local r3 = display.newRect( 30, 30, 25, 25 ); g1:insert(r3)
  local r4 = display.newRect( 40, 40, 25, 25 ); g2:insert(r4)
  local r5 = display.newRect( 50, 50, 25, 25 ); g2:insert(r5)
  local r6 = display.newRect( 60, 60, 25, 25 ); g2:insert(r6)
  local r7 = display.newRect( 70, 70, 25, 25 ); g3:insert(r7)
  local r8 = display.newRect( 80, 80, 25, 25 ); g3:insert(r8)
  local r9 = display.newRect( 90, 90, 25, 25 ); g3:insert(r9)
  -- clear all
  local stage = display.getCurrentStage()
  for i = stage.numChildren, 1, -1 do
    stage:remove(i)
  end
  -- watch for leaks
  collectgarbage()
  print(collectgarbage('count'))
end)


You will see the memory leak clearly happening on the console. If you move the require call past the patch code, all is good again.

In other words, the newGroup-patch may work most of the time and probably every time if you can ensure that the patch gets applied before any other module is loaded.

For me, the only real, fool-proof solution is if Ansca would hard-code the recursive removeSelf. Personally I cannot think of any use cases where you would want to hold on to display objects after the group they were in was deleted... and even if you have those cases where people want that, they should be forced to move their objects out of that group and put them in another before the group is removed.

Ansca are you listening? ;-) Would appreciate your take on this...

-FrankS.

uid: 8093 topic_id: 7489 reply_id: 26781


[TOPIC: post.html]
#25

p120ph37

[GLOBAL: userInfoPane.html]
p120ph37
  • Enthusiast

  • 46 posts
  • Corona SDK

Yes, in order for my method to be effective it ought to be included as the first "require" in main.lua, otherwise there is the risk that another library may localize the globals it patches before it can patch them.

This of course means that whoever is writing the code in main.lua needs to be aware of this dependence on the order of the requires, though I think for most scenarios this is not too large a burden to place on the coder, especially when the benefit is that misbehaving / leaking code may suddenly begin working as expected with no other change.

I've also done something similar with the Corona Remote code - I have created a library which patches the system globals and throws Runtime events so that the remote functionality can be patched transparently into an existing app which was written against the Corona accelerometer API with no code changes needed, rather than the typical method of writing your app specifically for remote.lua's API. (I can share that code too if anyone is interested)

My day job involves Perl, so I feel no remorse in twisting the internals of a dynamic language to do my bidding while remaining deceptively straightforward on the surface ;-) One (of the many) mottos of Perl is "Do what I mean, not what I say."
uid: 32962 topic_id: 7489 reply_id: 26853



[topic_controls]
Page 1 of 3 1 2 3
 
[/topic_controls]