Removing display object from table and then iterating over the table

@roaminggamer  Yes, thanks Ed.  Your grasp of the nuances of Lua and Corona often leads to informative lessons like this.  I appreciate the time you put into this response.  

Don’t give me too much credit.  At the end of the day, while I do have a wide experience in programming and game dev, my deep knowledge is somewhat lacking. 

I think that a real Lua expert would give a better answer and discussion of the costs and benefits.  

Whereas, my response is based more on my preferred usage and game architecture decisions. 

Also, I’m a lazy programmer and will willingly give up a little optimization for easy code writing and legibility.

Nice answer,I think it can be a separate article in coronalabs blog.

Hi,

Dont know how tight you need this to be but the first that comes to mind is to either sort it after removal, or maybe just switch the one you need to remove with the last one in the table, before removing it, hence maintaining a no gap table…just theory though, not tested.

You are nil’ling the item from in table which means the new end of the table (as far as # counting goes) is the index before it. In short, you’re removing it twice. Try this:

 local imageTable = {} local randomPosition = 2 local image1 = display.newImageRect("images/image1.png", 136, 46) imageTable[#imageTable + 1] = image1 print("Loaded first, table now has " .. #imageTable .. " entries.") local image2 = display.newImageRect("images/image2.png", 30, 30) imageTable[#imageTable + 1] = image2 print("Loaded second, table now has " .. #imageTable .. " entries.") local image3 = display.newImageRect("images/image3.png", 136, 46) imageTable[#imageTable + 1] = image3 print("Loaded third, table now has " .. #imageTable .. " entries.") display.remove(imageTable[randomPosition]) -- imageTable[randomPosition] = nil table.remove(imageTable, randomPosition) print("Deleted entry, table now has " .. #imageTable .. " entries.")

@horacebury:

I’m not sure how you mean… E.g. in this tutorial, it says that display.remove() is not enough, the object has to be set to nil as well: www.youtube.com/watch?v=NHefJ1mexrQ Also, in the docs for table.remove(), it doesn’t say anywhere that it sets the object to nil, it just removes it from the table itself. In other words, if I don’t explicitly set the display object to nil (the row you commented out), there will still be a reference to it. I thought that removing an object from a table is not the same thing as removing it from memory.

Or did I misunderstand you?

Alternately, you could NOT use numeric indexes and index by the object handle.

Copy and paste this example to see what I mean (may contain typos):

-- helper functin to count table entries local function tcount( t ) local c = 0 for k,v in pairs(t) do c = c + 1 end return c end -- table to store objs in local myobjs = {} -- touch handler that deletes touched object and shows table count before/after local function touch( self, event ) if( event.phase ~= "began" ) then return false end print("Before: ", tcount( myobjs ) ) -- FIXED TYPO myobjs[self] = nil display.remove( self ) print("After: ", tcount( myobjs ) ) -- FIXED TYPO return true end -- create some objects. for i =1, 10 do local tmp = display.newCircle( math.random( 100, 200 ), math.random( 100, 200 ), 10 ) tmp.touch = touch tmp:addEventListner( "touch" ) myobjs[tmp] = tmp end

You need to set all references to display objects to nil before they can get garbage collected (removed from memory.)

This means you need to set the variable ‘image2’ to nil and the entry in the ‘imageTable’.

By using ‘table.remove’ you are removing the entry in the table, which is like setting it to nil and moving all the items after it up one place, which overwrites that reference in the table. (i.e.: you don’t need to explicitly set a table’s reference to an image to nil before using table.remove.)

If I were you I would simply reference them from a display group. When you call display.remove() or image2:removeSelf() it will get automatically removed from the group.

@divergent,

The tutorial is either wrong or you’ve misunderstood it.

The rule is,  a display object cannot be garbage collected if there are any references to it.

For example, this object won’t be garbage collected:

local tmp local function test() tmp = display.newCircle( 10, 10, 10 ) display.remove( tmp ) end test() -- tmp still has reference to object so it wont' be collected.

this will be garbage collected:

local function test() local tmp = display.newCircle( 10, 10, 10 ) display.remove( tmp ) end test() -- tmp fell out of scope so it is collected

this will also be collected

local tmp local function test() tmp = display.newCircle( 10, 10, 10 ) display.remove( tmp ) tmp = nil end test() -- tmp is cleared in function and object can be garbage collected

So… what about tables…

local tmp = {} local function test() tmp[1] = display.newCircle( 10, 10, 10 ) tmp[2] = display.newCircle( 10, 10, 10 ) tmp[3] = display.newCircle( 10, 10, 10 ) display.remove( tmp[3] ) end test() -- tmp[3] object is not collected because the table still refers to it.

local tmp = {} local function test() tmp[1] = display.newCircle( 10, 10, 10 ) tmp[2] = display.newCircle( 10, 10, 10 ) tmp[3] = display.newCircle( 10, 10, 10 ) display.remove( tmp[3] ) -- remove object table.remove( tmp, 3 ) -- remove table entry end test() -- tmp[3] object IS collected because the table NO LONGER refers to it.

As one more possibility, I made this data structure available as a plugin, although it’s not a vanilla table and involves going through methods. It arose from some use cases where I was keeping track of things by index and thus they couldn’t shift around.

@horacebury

If I understand you correctly, tableRemove() not only removes an object from a table but it also sets the deleted reference in the table to nil?

@roaminggamer:

The code in my previous post was an attempt to make a simple demo but I forgot to take into account the scope factor. I’m very sorry about that. Please see below a more accurate (but longer) code structure with regard to my app.

If you run the code, you will see that it works if you touch the first or the third image, but if you touch the second, it will say that the table contains 1 entry after deletion (due to the nil gap). What’s so special about the second image?

If I comment out setting the table reference to nil in the function removeImage(), it works in all cases but then I don’t know if ALL references to the object are really deleted.

local createImage = {} local removeImage = {} local imageTable = {} local imageListener = {} function main() createImage(100) createImage(300) createImage(500) print("All done, table now has " .. #imageTable .. " entries") end function createImage(yPos) local image = display.newImageRect("images/image.png", 50, 50) image.x = display.contentCenterX image.y = yPos imageTable[#imageTable + 1] = image image:addEventListener("touch", imageListener) print("Created, table now has " .. #imageTable .. " entries.") end function removeImage(tableToRemoveFrom, object) for i = 1, #tableToRemoveFrom do if (tableToRemoveFrom[i] == object) then display.remove(tableToRemoveFrom[i]) tableToRemoveFrom[i] = nil table.remove(tableToRemoveFrom, i) end end end function imageListener:touch(event) timer.performWithDelay(10, function() removeImage(imageTable, event.target) print("Deleted, table now has " .. #imageTable .. " entries") end, 1) end

You cannot (safely) remove entries from a numerically indexed table while iterating over it in the FORWARD direction (more on this below).
 
I am guessing, but when you remove an entry from a numerically-indexed table while iterating it, you essentially collapse the table and the iterator now has a stale view of the structure of the table.
 
 
 
So, how can you fix this?

function removeImage(tableToRemoveFrom, object) local ri local i = 1 while( ri == nil and i \<= #tableToRemoveFrom) do if (tableToRemoveFrom[i] == object) then ri = i; end i = i + 1 end if( not ri ) then return end -- The order of the next two statements does not matter display.remove(object) table.remove(tableToRemoveFrom, ri) end

Notice I did not set the entry to ‘nil’. You don’t need to do that. The entry is gone the reference is gone. Fin.

What about REVERSE iteration?

Another easy way to deal with this is simply to reverse iterate over the table.  Removing objects and entries while reverse iterating will avoid the ‘collapsing’ issue you are encountering.

Before you ask, let me posit the follow-on question: “How can I iterate over a table and remove multiple display object entries that meet a criteria?”

The answer is simple:

local function removeEntries( t, test ) local toRemove = {} for i = #t, 1, -1 do if( test( t[i] ) then toRemove[#toRemove+1] = i end for i = 1, #toRemove do local index = toRemove[i] display.remove(t[index]) table.remove( t, index ) end end

In case you missed it, I amended my prior post (2 back) with an update about iteration directions.

What about REVERSE iteration?

 

Another easy way to deal with this is simply to reverse iterate over the table.  Removing objects and entries while reverse iterating will avoid the ‘collapsing’ issue you are encountering.

 

Reverse iteration also simplifies the multiple entry removal case.

function removeImage(tableToRemoveFrom, object) for i = #tableToRemoveFrom, 1, -1 do if (tableToRemoveFrom[i] == object) then display.remove(tableToRemoveFrom[i]) --tableToRemoveFrom[i] = nil -- NOT NEEDED table.remove(tableToRemoveFrom, i) end end end

@roaminggamer:

Reversing the iteration seemed the simplest and most logical way of solving this. I pasted the function in your last post into my code and ran some tests. It all works now…

Two last questions to wrap it all up:

  1. Is the reason that it is not necessary to set the table reference to nil that table.remove() does that (as horacebury wrote)?

  2. Given that table.remove() potentially can slow down performance (due to the shuffling), is there any other, faster way of doing this if I want to use it in a time critical situation (still using tables)? 

Many thanks, I really appreciate (as always) that you have taken the time to answer!

Regarding #1, yes.

Variables (and here I include table entries) hold a value. When you assign to a variable, you drop a reference to its

current value and add one to its replacement. If that was the only remaining reference to the old value, it becomes unreachable

and the garbage collector is free to reclaim it.

In this respect, specifically assigning nil only truly matters in the case of table entries. Then the entry is indistinct from one

that was never assigned, so the table logic can just as well remove it (the above details then apply to its key as well, say if you used table keys as mentioned above by roaminggamer ).

The upshot being that if you want to keep the array structure intact you can simply assign some value other than nil to indicate removed elements. The onus is then on you to handle it, of course.

As an example:

local MyTable = {} function Update () &nbsp; for i, entry in ipairs(MyTable) do &nbsp; &nbsp; if entry then -- using 'false' as sentinel, ignore if missing &nbsp; &nbsp; &nbsp; entry:UpdateMe() &nbsp; if entry:IsDead() then &nbsp; &nbsp; &nbsp; &nbsp; MyTable[i] = false -- ignore this slot afterward &nbsp; &nbsp; &nbsp; end &nbsp; &nbsp; end &nbsp; end end

(The “stable array” plugin uses an alternative along these lines: removed entries are replaced by indices into a free list, so you can quickly reclaim freed-up slots.)

#2 - Yes, use objects as indices.  I never use numeric indices for storing objects unless:

A - I am iterating over huge numbers of display objects (10K+; rare for display objects).

  • OR - 

B - Ordering is required for some reason.

I am very thankful for this conversation.  It is explaining some anomalies in my projects that I couldn’t quite put my finger on before.  @roaminggamer I commonly use numeric indices for tables because it seems like an easy way to build tables

[lua]

local myTable = {}

local circle = display.newCircle( 10, 10, 10 )

myTable[#myTable + 1] = circle

[/lua]

If I’m understanding your reasoning, you would likely never do this?  I feel like I’ve seen this approach to building tables so often that it seems commonplace - but I see now that it comes with a price.  Thanks again, I’ve got some rewriting to do. . . 

Actually, I just saw that you do have a use for method described above in the quote below.  Is this a quick and dirty fix for removing multiple objects that meet the same criteria or is it how you would actually use it in your own projects?  I’m curious as it is a number index.

It’s not that I want to use specifically numeric indexes in tables, but as sporkfin also mentions, it seems as the easiest way. I don’t have to keep track of what objects I used as indexes and iterating also seems more cumbersome without numeric indexes. Apart from the deletion problem issue this thread, I cannot se any disadvantages to using numeric indexes. Or have I missed something?

@sporkfin

I think your code got mangled.  Re-pasting on multi-lines:

local myTable = {} local circle = display.newCircle( 10, 10, 10 ) myTable[#myTable + 1] = circle

While there is nothing wrong with this way of populating tables, I find it very inflexible and painful to operate on.

Why Do We Put Object References In Tables

Before we can have  meaningful discussion about this, we need to agree on why we are putting the references in tables in the first place.

I will posit two examples:

  • Targeting - Often, in action games, I keep a table of enemies.  Then, if my player is firing a ‘seeking projectile’ I can iterate over the table of ‘targets/enemies’ and select one that meets whatever criteria I need.
  • Asteroids’ - Another great example is the game Asteroids.  In this game, you progressively have more and more asteroids in the world as the difficulty gets higher.  Additionally, your implementation of the game may need to test for a minimum count to advance the difficulty of the game.

When Will The Objects Be Removed and What Will I Know At the Time?

  • In BOTH of the above cases, the objects in the tables are going to be removed from the world in arbitrary order.
  • When I remove an object from the world I will have a reference to that object in my possession. i.e. My code will know what object I am removing.

Why Object Referencing Is Better (In This Case)

When I go to remove the display object(s) in the above scenarios I need to do the following:

  1. Destroy the display object(s).
  2. Locate their entry in the ‘tracking’ table and remove (clear) the reference.
  3. (Optionally) remove the entry from the table.  ( TIP:  If I do this, #2 is automatically achieved).

We talked about this above, but these steps are required so:

  • The object(s) is properly destroyed AND garbage collected.
  • The table can still be iterated over.

The challenge here is, if I’m using a numerically indexed table, I have to either:

  • Iterate over the table in reverse order to avoid creating gaps that impede the iteration.  - Not hard, but unnatural for me, and the lazy programmer in me hates the extra typing. 

  • OR - 

  • Write some complex code or wrapper to defer the clearing of entries till after identifying them (the code you asked about above is an example of this approach.)

Therefore, I find it FAR SIMPLER to simply do this:

local myTable = {} local tmp= display.newCircle( 10, 10, 10 ) myTable[tmp] = tmp

Now, removing entries from the table is straight-forward:

-- One nice way do handle this is to have a finalize function for the object function tmp.finalize( self ) myTable[self] = nil end tmp:addEventListener("finalize") -- Now if we delete the object, its entry in the table is automatically cleared

You could also do that clearing in the place you actually delete the object.  For example, if you delete the object in its collision handler:

function tmp.collision( self, event ) if( event.phase ~= "began" ) then return false end display.remove( self ) myTable[self] = nil return true end tmp:addEventListener("collision")

The key thing to remember is, wherever you do this work, you must ensure the tracking table is in  scope.  That is it.

What Are The Drawbacks To Object Indexed Tables?

  • Counting entries is a little more work:

    local function tcount( t ) local c = 0 for k,v in pairs(t) do c = c + 1 end return c end

  • “Iteration is slower!” - I put an exclamation point on that because I often see people state this as if somehow it is a big deal.  It almost never is.

To be clear, I’m saying this style:

for k,v in pairs(tbl) do end

is slower than this:

for i = 1, #tbl do end

So what?  Unless you’re iterating over 10K entries per frame this won’t make a bit of difference to your game speed.  i.e. You only notice this in the extreme cases.

  • “What about the table size and the way it grows?” - Honestly, I don’t know much about this.  I suspect:
    • Numeric indexing may be more efficient.
    • There may be hidden memory and computing costs associated with object indexing.

However, at the end of the day, I still say this will be MOOT and barely noticeable.

I’m done

I think I’m done on this topic and I hope I’ve answered any open questions as well as explained why I prefer object indexing over numeric.