Safely remove display/physics objects from table

I’m quite confused, and I don’t know why the Corona online example removes objects from table this way:

(http://docs.coronalabs.com/api/type/DisplayObject/removeSelf.html)
– Manually cull offscreen objects

  1. local function removeOffscreenItems()
  2.     for i = 1, #allItems do
  3.         local anItem = allItems[i]
  4.         if (anItem.x) then                        
  5.             if anItem.x < -100 or anItem.x > display.contentWidth + 100
  6.                 or anItem.y < -100 or anItem.y > display.contentHeight + 100 then
  7.                     anItem:removeSelf()
  8.             end     
  9.         end
  10.     end

It doesn’t set anItem to nil.  Does not loop through table in reverse, and it doesn’t actually remove the objects from the table, correct?  I tried it and it doesn’t seem to work.

I’ve got the bottom two code segments to work, but wanted to make sure with the pros, if I was doing it right, or what the proper way is:

  1.     for i=#self.allStuff,1, -1 do
  2.         if self.allStuff[i] then
  3.             if (self.allStuff[i].y > screen.bottom) or
  4.                 (self.allStuff[i].y < screen.top) or
  5.                 (self.allStuff[i].x < screen.left) or
  6.                 (self.allStuff[i].x > screen.right) then
  7.                     oneStuff = self.allStuff[i]
  8.                     display.remove (self.allStuff[i]) – is this line better
  9.                     --oneStuff:removeSelf()  – or is this line better?
  10.                     --self.allStuff[i]:removeSelf() – why is this line wrong?
  11.                     table.remove (self.allStuff,i)
  12.                     oneStuff = nil
  13.             end
  14.         end
  15.     end
  16. ---- or --------- ?
  17.     for i=#self.allStuff,1, -1 do
  18.         if self.allStuff[i] then
  19.             if (self.allStuff[i].y > screen.bottom) or
  20.                 (self.allStuff[i].y < screen.top) or
  21.                 (self.allStuff[i].x < screen.left) or
  22.                 (self.allStuff[i].x > screen.right) then
  23.                      local oneStuff = table.remove (self.allStuff,i)
  24.                      if oneStuff ~=nil then    
  25.                          oneStuff:removeSelf()
  26.                          oneStuff=nil
  27.                      end
  28.             end
  29.         end
  30.     end
     

@eik,

There are myriad ways to do this, as you’ve discovered, some good, some better. 

I would suggest this as a fast + safe way to do it.

local allStuff = self.allStuff -- Get reference for (slight) speed-up &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-- Skips 'self.' indirect access on each loop local oneStuff -- Create the local outside the loop to save -- local creation/destruction cost in loop -- No need for an elaborate iterator unless you want it for another reason. for i = 1, #allStuff do &nbsp; &nbsp;if allStuff[i] then &nbsp; &nbsp; &nbsp; if (allStuff[i].y \> screen.bottom) or &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(allStuff[i].y \< screen.top) or &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(allStuff[i].x \< screen.left) or &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(allStuff[i].x \> screen.right) then &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;oneStuff = allStuff[i] &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; allStuff[i] = nil -- Clears table entry, won't affect iterator &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; display.remove( oneStuff ) -- Safest way to remove object. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- No need to nil the variable oneStuff. &nbsp;Why? &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- The next time we use it we are in essence clearing it -- and releasing the reference to&nbsp;Lua garbage collection. &nbsp; -- Also, we will clear in one (last) time after the loop. &nbsp; &nbsp; &nbsp; end &nbsp; &nbsp;end end oneStuff -- Don't forget to clear oneStuff to free the last removed object&nbsp;

raominggamer, thank you for the detailed explanation and the code. 

However, when I tried the above code, some of the objects remained on screen when I tried it with

  1.       if (allStuff[i].y > screen.CENTER) or
  2.          (allStuff[i].y < screen.top) or
  3.          (allStuff[i].x < screen.left) or
  4.          (allStuff[i].x > screen.right) then

All my objects are falling from top to bottom, and they should be removed when past halfway of the screen.  However, some of the objects remained and continue beyond screen.CENTER.

eik,

Hi.  I’m sorry, but I must admit I ignored the reason for the deletion and focused on the safest way to delete the object instead.  

If the game gets past the if-statement, the objects will be deleted.

Question: Why are you checking x-position?  If you want to delete an object that passes the halfway mark (moving downward), just check this:

if( allStuff[i].y \> display.contentCenterY ) then ... deletion code here

Cheers,

Ed

Some of the objects are pushed/bounced in random directions as they fall, so I check the x as well.

I restarted the simulator without changing code, and now it is working; objects are removed halfway beyond screen. 

Don’t know why the simulator did that before. 

I’m still confused why table.remove is not needed.  This doesn’t leave “holes” in the table?

Do I need to compact the table somehow?

I’ve suggested to the documenation team that they look at either redo’ing that culling example or removing it since we actually cull off screen items anyway.

Realistically, it was a simple way of showing you how to use removeSelf() and some times things that are good examples are not good in real-world applications.  But thanks for pointing this out and we will make it better.

Rob

@eik

Tables are kinda funny in Lua.  First, you can index them in two ways:

  1. Using whole integers

    local myTbl = {} myTbl[1] = “Lua tables are” myTbl[#myTbl + 1] = " tricky" print( myTble[1] … myTbl[#myTbl] ) – Prints: Lua tables are tricky print( myTble[1] … myTbl[2] ) – Also prints: Lua tables are tricky

  2. Using strings (or other non-integer values)

    local myTbl = {} myTbl[“Ed”] = “Lua tables are” myTbl.says = " tricky" print( myTble[“Ed”] … myTbl[“says”] ) – Prints: Lua tables are tricky print( myTble.Ed … myTbl.says )       – Also prints: Lua tables are tricky 

Second, once you get comfortable with this, you may start to worry about indexing speed and table growth (as you are now).  

I won’t go into it here (you can read this document: http://www.lua.org/gems/sample.pdf [see page 19++]), but Lua table growth is tricky and fluid.  

There are better and worse ways of building them.  If you know how your table will grow over time, you can pre-allocate space and do some other smart things.  

Having said that, “I seriously doubt that table creation/destruction/access performance will be the most critical part of your game. Thus, I would focus on other aspects and instead use tables in the way you find easiest and most natural.”

To finally answer your question about gaps, “No, if you’re using integer index values and you set the value of ‘middle’ entry to nil, the table will automatically ‘close up ranks’.  The memory used to index the table will not likely shrink, but it will be temporarily unused.”

For example, try this code:

local myTbl = { 1, 2, 3, 4, 'a', 5, 'a', 6, 'a', 7, 8, 9, 'a' } print("Entries in myTbl == " .. #myTbl ) for i = 1, #myTbl do print( i, myTbl[i] ) if( myTbl[i] == 'a') then myTbl[i] = nil end end print("Entries in myTbl == " .. #myTbl ) for i = 1, #myTbl do print( i, myTbl[i] ) end

Cheers,

Ed

PS - For the best speed, use integer indicies, and set to nil to clear an entry.  Don’t forget to delete objects separately (as shown in my prior post.)  Alternately, use ‘other’ indicies to simplify some coding scenarios.  I frequently use strings and the ‘dot’ notation you saw me use in the strings example above.  I also use object IDs as indicies pointing to ‘values’ also containing the ID.

local myTbl = {} local tmp = display.newCircle( 0, 0, 10 ) tmp.someFlag = true myTbl[tmp] = tmp -- Circle ID is index and value in this table -- later... for k,v in pairs( myTbl ) -- Warning: pairs() is MUCH slower than integer indexing if(v.someFlag) then myTbl[k] = nil display.remove( v ) end end

@eik,

There are myriad ways to do this, as you’ve discovered, some good, some better. 

I would suggest this as a fast + safe way to do it.

local allStuff = self.allStuff -- Get reference for (slight) speed-up &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-- Skips 'self.' indirect access on each loop local oneStuff -- Create the local outside the loop to save -- local creation/destruction cost in loop -- No need for an elaborate iterator unless you want it for another reason. for i = 1, #allStuff do &nbsp; &nbsp;if allStuff[i] then &nbsp; &nbsp; &nbsp; if (allStuff[i].y \> screen.bottom) or &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(allStuff[i].y \< screen.top) or &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(allStuff[i].x \< screen.left) or &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(allStuff[i].x \> screen.right) then &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;oneStuff = allStuff[i] &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; allStuff[i] = nil -- Clears table entry, won't affect iterator &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; display.remove( oneStuff ) -- Safest way to remove object. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- No need to nil the variable oneStuff. &nbsp;Why? &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- The next time we use it we are in essence clearing it -- and releasing the reference to&nbsp;Lua garbage collection. &nbsp; -- Also, we will clear in one (last) time after the loop. &nbsp; &nbsp; &nbsp; end &nbsp; &nbsp;end end oneStuff -- Don't forget to clear oneStuff to free the last removed object&nbsp;

raominggamer, thank you for the detailed explanation and the code. 

However, when I tried the above code, some of the objects remained on screen when I tried it with

  1.       if (allStuff[i].y > screen.CENTER) or
  2.          (allStuff[i].y < screen.top) or
  3.          (allStuff[i].x < screen.left) or
  4.          (allStuff[i].x > screen.right) then

All my objects are falling from top to bottom, and they should be removed when past halfway of the screen.  However, some of the objects remained and continue beyond screen.CENTER.

eik,

Hi.  I’m sorry, but I must admit I ignored the reason for the deletion and focused on the safest way to delete the object instead.  

If the game gets past the if-statement, the objects will be deleted.

Question: Why are you checking x-position?  If you want to delete an object that passes the halfway mark (moving downward), just check this:

if( allStuff[i].y \> display.contentCenterY ) then ... deletion code here

Cheers,

Ed

Some of the objects are pushed/bounced in random directions as they fall, so I check the x as well.

I restarted the simulator without changing code, and now it is working; objects are removed halfway beyond screen. 

Don’t know why the simulator did that before. 

I’m still confused why table.remove is not needed.  This doesn’t leave “holes” in the table?

Do I need to compact the table somehow?

I’ve suggested to the documenation team that they look at either redo’ing that culling example or removing it since we actually cull off screen items anyway.

Realistically, it was a simple way of showing you how to use removeSelf() and some times things that are good examples are not good in real-world applications.  But thanks for pointing this out and we will make it better.

Rob

@eik

Tables are kinda funny in Lua.  First, you can index them in two ways:

  1. Using whole integers

    local myTbl = {} myTbl[1] = “Lua tables are” myTbl[#myTbl + 1] = " tricky" print( myTble[1] … myTbl[#myTbl] ) – Prints: Lua tables are tricky print( myTble[1] … myTbl[2] ) – Also prints: Lua tables are tricky

  2. Using strings (or other non-integer values)

    local myTbl = {} myTbl[“Ed”] = “Lua tables are” myTbl.says = " tricky" print( myTble[“Ed”] … myTbl[“says”] ) – Prints: Lua tables are tricky print( myTble.Ed … myTbl.says )       – Also prints: Lua tables are tricky 

Second, once you get comfortable with this, you may start to worry about indexing speed and table growth (as you are now).  

I won’t go into it here (you can read this document: http://www.lua.org/gems/sample.pdf [see page 19++]), but Lua table growth is tricky and fluid.  

There are better and worse ways of building them.  If you know how your table will grow over time, you can pre-allocate space and do some other smart things.  

Having said that, “I seriously doubt that table creation/destruction/access performance will be the most critical part of your game. Thus, I would focus on other aspects and instead use tables in the way you find easiest and most natural.”

To finally answer your question about gaps, “No, if you’re using integer index values and you set the value of ‘middle’ entry to nil, the table will automatically ‘close up ranks’.  The memory used to index the table will not likely shrink, but it will be temporarily unused.”

For example, try this code:

local myTbl = { 1, 2, 3, 4, 'a', 5, 'a', 6, 'a', 7, 8, 9, 'a' } print("Entries in myTbl == " .. #myTbl ) for i = 1, #myTbl do print( i, myTbl[i] ) if( myTbl[i] == 'a') then myTbl[i] = nil end end print("Entries in myTbl == " .. #myTbl ) for i = 1, #myTbl do print( i, myTbl[i] ) end

Cheers,

Ed

PS - For the best speed, use integer indicies, and set to nil to clear an entry.  Don’t forget to delete objects separately (as shown in my prior post.)  Alternately, use ‘other’ indicies to simplify some coding scenarios.  I frequently use strings and the ‘dot’ notation you saw me use in the strings example above.  I also use object IDs as indicies pointing to ‘values’ also containing the ID.

local myTbl = {} local tmp = display.newCircle( 0, 0, 10 ) tmp.someFlag = true myTbl[tmp] = tmp -- Circle ID is index and value in this table -- later... for k,v in pairs( myTbl ) -- Warning: pairs() is MUCH slower than integer indexing if(v.someFlag) then myTbl[k] = nil display.remove( v ) end end