Removing objects from table

I am trying to remove objects from a table by looping through it. Though the main purpose of removing objects this way is to clean up memory once a scene is destroyed but here is a demo code to demonstrate what issue I am facing.

I am spawning ten rectangles and storing reference of each spawned rectangle in rects table. I am attaching a ‘tap’ event to each rectangle so that they are removed from display and table once they are tapped. When seven rectangles are tapped, I want to remove all the rectangles from display and rects table as well. The issue is that sometimes all the rectangles are removed once I tap seven out of them randomly, but sometimes some of the rectangles are not removed.

Here is the code

local composer = require("composer" ) local scene = composer.newScene() local rects = {} local removedRects =0 local function removeAllRects() print(#rects) print("===============================================") for i=#rects, 1, -1 do print(rects[i]) if rects[i] then print("Removing"..i) rects[i]:removeSelf( ) rects[i] = nil end end rects = nil print("all rects removed") end local function removeRect( event) removedRects = removedRects + 1 print("----------------------------------") local id = event.target.id rects[id]:removeSelf( ) rects[id] = nil print("Table size") print(#rects) for i=#rects, 1, -1 do print(rects[i]) if rects[i] then print(rects[i].id) end end if removedRects == 7 then removeAllRects() end end local function spawnRects( ) for i=1, 10, 1 do rects[i] = display.newRect( display.contentWidth/2, i\*50, 20, 20 ) rects[i].id = i rects[i]:addEventListener( "tap", removeRect ) end end function scene:create( event ) -- body end function scene:show( event ) if event.phase == "did" then spawnRects() end end function scene:hide( event ) -- body end function scene:destroy( event ) -- body end scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) return scene 

I am printing some messages to console to keep track of thing. What I found is bit confusing.

The table size the I am printing using #rects most of the time returns 10 but sometimes some other number after tapping a rectangle. What I expected it to return either 10 all the times when I tap a rectangle or the size should decrement by one, once a rectangle is removed. But it either returns 10 or randomly decrement.

What am I missing? 

Hi @zahidbashirkhan,

I think there may be some issue in how you’re removing individual rectangles, using the “id” which is not necessarily the same index in the table, so the items are getting “out of order”. For example, let’s say you have these rectangles in proper order at the start (list of “.id” property shown here):

[lua]

{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }

[/lua]

Then, let’s say the user taps on #8… you remove the item at position #8, and you have this:

[lua]

{ 1, 2, 3, 4, 5, 6, 7, 9, 10 }

[/lua]

But now, rectangle with “.id” of 9 is actually the 8th item in the table, so if you later try to remove #9, it’s not in position #9 (id #10 is there instead). 

If you want to remove a table item by “name” (or “id” in this case, since you assigned that property), you may want to try a looping removal function similar to this:

[lua]

local function removeRect( event )

    for i=1,#rects do

        local v = rects[i]

        if ( v and v.id == event.target.id ) then

            table.remove( rects,i )

            break

        end

        v = nil

    end

    return false

end

[/lua]

What this does is locate the index (position) of the named item within the table, and it removes it, then the loop breaks so that no further processing effort is wasted once the removal occurs.

By the way, your “removeAllRects()” function looks completely fine (you understand to loop through backwards, kudos!).

Brent

Thanks @Brent,

Nice explanation. Just added one more line v:removeSelf() to your code for removeRect in order to remove the rectangle from screen as well

local function removeRect( event ) for i=1,#rects do local v = rects[i] if ( v and v.id == event.target.id ) then v:removeSelf( ) table.remove( rects,i ) break end v = nil end return false end

Also, keep in mind that if you do indexed arrays, if you nil out a member in the middle, the # operator only counts up to the first nil:

t = {  2, 3, 5, 8, 9, 1, 4, 6 }

print(#t) – outputs  8

t[4] = nil

print(#t) – outputs 3

You need to use table.maxn() to loop over a numerically indexed table with nil’s in the middle.

Rob

I have implemented the above suggested solution in my sample game but faced another issue. I could have posted it as a new post but since it is in relation to this post therefore posting here.

I made some changes to the above demo code and change the behavior of the demo app a little. Now I am spawning rectangle with each spawned rectangle transitioned from right to left. I have also added a floor to the app. When a user tap a rectangle, its transition is cancelled and physics is applied to the rectangle, so it falls to the floor. I have added an event listener to the floor to detect the collision of the rectangle and remove the rectangle by using the method suggested above. The aim is to remove all rectangles once a specific number of rectangles fall on floor (but I didn’t include this last functionality in demo code in order to keep the code short here). The problem that I am facing is that, initially a couple of rectangles are immediately removed when they collide the floor, but the others just bounce back from the floor and are not removed using the above suggested method.

Here is the complete code you can run

local composer = require("composer" ) local scene = composer.newScene() local physics = require("physics") physics.start( ) local rects = {} local numRects = 0 local removedRects =0 local floor local function rectTap( event) transition.cancel(event.target) physics.addBody( event.target, "kinametic", {density=1.0, friction=0.2, bounce=0 } ) event.target:applyForce( 0, 10, event.target.x, event.target.x ) end local function spawnRects( ) numRects = numRects + 1 rects[numRects] = display.newRect( 0, 0, 40, 40 ) rects[numRects].id = numRects rects[numRects].type = "rect" rects[numRects].x = math.random(display.contentWidth + 50, display.contentWidth + 100 ) rects[numRects].y = math.random( 70,100) rects[numRects].transition = transition.to( rects[numRects], {time=9000, x = -rects[numRects].width } ) rects[numRects]:addEventListener( "tap", rectTap ) end local function onCollision( event ) print("collision occured") print("event.other.id") print(event.other.id) if event.target.type == "floor" and event.other.type=="rect" then for i = 1, #rects , 1 do local v = rects[i] if v and v.id == event.other.id then print("removing single rect") event.other:removeSelf( ) table.remove( rects, i ) break end end end end function scene:create( event ) floor = display.newRect( display.contentWidth/2, display.contentHeight, display.contentWidth + 500, 150 ) floor:setFillColor( 0.3137254901960784, 0.592156862745098, 0.23529411764705882 ) floor.type="floor" physics.addBody( floor, "static") end function scene:show( event ) if event.phase == "did" then timer.performWithDelay( 3000, spawnRects, -1) floor:addEventListener( "collision", onCollision ) end end function scene:hide( event ) -- body end function scene:destroy( event ) -- body end scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) return scene

it would be greatly appreciated if you could kindly comment on this behavior.

Regards,

Ack… deleted original post

 

Well, I had a bunch of advice, but let me just summarize this time around:

  • Table listeners better than function listeners.
  • display.remove() safer than obj:removeSelf()
  • Don’t make file-level (or global) references to objects unless you absolutely need to.
  • Insert objects into composer group or no purpose in using composer.
  • Let composer clean up your objects.  Its safer.
  • “kinematic”  not “kenematic”, but wrong type anyways.  Kinematic bodies don’t respond to gravity or forces.  I suggest “dynamic”

Sorry for being ‘terse’.  I just goofed and deleted a much nicer and verbose response…sigh.

Here is a reworked version of your code:

-- ============================================================= -- Nicer Composer Framework -- ============================================================= -- -- ============================================================= local composer = require( "composer" ) local scene = composer.newScene() local physics = require("physics") physics.start( ) ---------------------------------------------------------------------- -- LOCALS -- ---------------------------------------------------------------------- -- Variables local spawnTimer -- Forward Declarations (allows you to reference functions before defining them) local rectTap local spawnRects local onCollision ---------------------------------------------------------------------- -- Scene Methods ---------------------------------------------------------------------- ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:create( event ) local screenGroup = self.view local floor = display.newRect( screenGroup, display.contentWidth/2, display.contentHeight, display.contentWidth + 500, 150 ) floor:setFillColor( 0.3137254901960784, 0.592156862745098, 0.23529411764705882 ) floor.type="floor" physics.addBody( floor, "static") end ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:willEnter( event ) local screenGroup = self.view end ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:didEnter( event ) local screenGroup = self.view -- Used a closure to call the function and pass in screenGroup spawnTimer = timer.performWithDelay( 3000, function() spawnRects(screenGroup) end , -1) end ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:willExit( event ) end ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:didExit( event ) end ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:destroy( event ) local screenGroup = self.view -- Stop that timer timer.cancel( spawnTimer ) spawnTimer = nil end ---------------------------------------------------------------------- -- FUNCTION/CALLBACK DEFINITIONS -- ---------------------------------------------------------------------- rectTap = function ( self, event ) transition.cancel( self.myTransition ) physics.addBody( self, "dynamic", {density=1.0, friction=0.2, bounce=0 } ) self:applyForce( 0, 10, self.x, self.x ) -- Note: I put the collision listener on the block instead tmp.collision = onCollision tmp:addEventListener( "collision") end spawnRects = function( group ) local tmp = display.newRect( group, 0, 0, 40, 40 ) tmp.id = numRects tmp.type = "rect" tmp.x = math.random(display.contentWidth + 50, display.contentWidth + 100 ) tmp.y = math.random( 70,100) tmp.myTransition = tmp.transition = transition.to( rects[numRects], {time=9000, x = -rects[numRects].width } ) tmp.tap = rectTap tmp:addEventListener( "tap", tmp ) end onCollision = function ( self, event ) print("onCollision()") display.remove(self) end --------------------------------------------------------------------------------- -- Scene Dispatch Events, Etc. - Generally Do Not Touch Below This Line --------------------------------------------------------------------------------- function scene:show( event ) local screenGroup = self.view local willDid = event.phase if( willDid == "will" ) then self:willEnter( event ) elseif( willDid == "did" ) then self:didEnter( event ) end end function scene:hide( event ) local screenGroup = self.view local willDid = event.phase if( willDid == "will" ) then self:willExit( event ) elseif( willDid == "did" ) then self:didExit( event ) end end scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) --------------------------------------------------------------------------------- return scene

Thanks a lot  @roaminggamer for your time and such a detailed reply.  

@roaminggamer, a little late but here are my findings (confusions) about the code (bear with me as I am new to corona and lua)

1. why in scene:create() and other listeners its local screenGroup and in show screenGroup (without local)

  1. I am getting an error at line ’ tmp.addEventListener( “tap”, tmp ) as attempt to call method respondsToEvent nil value. Additionally what is the difference between tmp:addEventListener and tmp.addEventListener (. and :  .)

  2. If I modify the logic to call the scene:destroy  by adding one global variable ‘count’ and then changing the onCollision as 

    onCollision = function ( self, event ) print(“onCollision()”) count = count + 1 display.remove(self) if count == 3 then composer.removeScene(“scene6”) end end

 I get an error when the scene is destroyed

bad argument to newRect, proxy expected got nil at line ‘local tmp = display.newRect( group, 0, 0, 40, 40 )’

Appreciate your help

Regards,

  1.  Are you looking at the code in this post (i.e. not attached to an e-mail as an update notice).  Always examine the code from the blog.  Why?  Because when you get a notification, it gives your the code/comments from the post, but not subsequent fixes.

I tend to make typos, see them, re-edit the post, and re-post.  You won’t see any of these corrections unless you look at the blog on the forums.

That said, I should have put a ‘local’ in front of each definition of ‘screenGroup’.

  1. I don’t use tap myself, as I prefer touch.  Typo: “tmp.addEventListener( “tap”, tmp )”  I should have typed “tmp:addEventListener( “tap”, tmp )”

I’ll fix that now.

  1.  You need to cancel the spawn timer first or it will try to add to a non-existent group:

    – Stop that timer timer.cancel( spawnTimer ) spawnTimer = nil – Now remove the scene composer.removeScene( “scene6” )

It should be local screenGroup = self.view

tmp.someMethod(someParam) does not pass a reference (self) to the object to the method, only the specified parameter.  tmp:someMethod(someParam) does pass the object in as self.  tmp:someMethod( someParam ) is identical to tmp.someMethod( tmp, someParam) in calling and internally if you use : you don’t have to specify self, it’s there automatically.

Ed, I’m not sure what’s up with your posts getting mixed in out of order.

Hey there !

Just tried your code, and in both cases, outputs 8… Is is normal ?

Interesting.  This used to be an issue.  It doesn’t seem to be an issue now.

Hi @zahidbashirkhan,

I think there may be some issue in how you’re removing individual rectangles, using the “id” which is not necessarily the same index in the table, so the items are getting “out of order”. For example, let’s say you have these rectangles in proper order at the start (list of “.id” property shown here):

[lua]

{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }

[/lua]

Then, let’s say the user taps on #8… you remove the item at position #8, and you have this:

[lua]

{ 1, 2, 3, 4, 5, 6, 7, 9, 10 }

[/lua]

But now, rectangle with “.id” of 9 is actually the 8th item in the table, so if you later try to remove #9, it’s not in position #9 (id #10 is there instead). 

If you want to remove a table item by “name” (or “id” in this case, since you assigned that property), you may want to try a looping removal function similar to this:

[lua]

local function removeRect( event )

    for i=1,#rects do

        local v = rects[i]

        if ( v and v.id == event.target.id ) then

            table.remove( rects,i )

            break

        end

        v = nil

    end

    return false

end

[/lua]

What this does is locate the index (position) of the named item within the table, and it removes it, then the loop breaks so that no further processing effort is wasted once the removal occurs.

By the way, your “removeAllRects()” function looks completely fine (you understand to loop through backwards, kudos!).

Brent

Thanks @Brent,

Nice explanation. Just added one more line v:removeSelf() to your code for removeRect in order to remove the rectangle from screen as well

local function removeRect( event ) for i=1,#rects do local v = rects[i] if ( v and v.id == event.target.id ) then v:removeSelf( ) table.remove( rects,i ) break end v = nil end return false end

Also, keep in mind that if you do indexed arrays, if you nil out a member in the middle, the # operator only counts up to the first nil:

t = {  2, 3, 5, 8, 9, 1, 4, 6 }

print(#t) – outputs  8

t[4] = nil

print(#t) – outputs 3

You need to use table.maxn() to loop over a numerically indexed table with nil’s in the middle.

Rob

I have implemented the above suggested solution in my sample game but faced another issue. I could have posted it as a new post but since it is in relation to this post therefore posting here.

I made some changes to the above demo code and change the behavior of the demo app a little. Now I am spawning rectangle with each spawned rectangle transitioned from right to left. I have also added a floor to the app. When a user tap a rectangle, its transition is cancelled and physics is applied to the rectangle, so it falls to the floor. I have added an event listener to the floor to detect the collision of the rectangle and remove the rectangle by using the method suggested above. The aim is to remove all rectangles once a specific number of rectangles fall on floor (but I didn’t include this last functionality in demo code in order to keep the code short here). The problem that I am facing is that, initially a couple of rectangles are immediately removed when they collide the floor, but the others just bounce back from the floor and are not removed using the above suggested method.

Here is the complete code you can run

local composer = require("composer" ) local scene = composer.newScene() local physics = require("physics") physics.start( ) local rects = {} local numRects = 0 local removedRects =0 local floor local function rectTap( event) transition.cancel(event.target) physics.addBody( event.target, "kinametic", {density=1.0, friction=0.2, bounce=0 } ) event.target:applyForce( 0, 10, event.target.x, event.target.x ) end local function spawnRects( ) numRects = numRects + 1 rects[numRects] = display.newRect( 0, 0, 40, 40 ) rects[numRects].id = numRects rects[numRects].type = "rect" rects[numRects].x = math.random(display.contentWidth + 50, display.contentWidth + 100 ) rects[numRects].y = math.random( 70,100) rects[numRects].transition = transition.to( rects[numRects], {time=9000, x = -rects[numRects].width } ) rects[numRects]:addEventListener( "tap", rectTap ) end local function onCollision( event ) print("collision occured") print("event.other.id") print(event.other.id) if event.target.type == "floor" and event.other.type=="rect" then for i = 1, #rects , 1 do local v = rects[i] if v and v.id == event.other.id then print("removing single rect") event.other:removeSelf( ) table.remove( rects, i ) break end end end end function scene:create( event ) floor = display.newRect( display.contentWidth/2, display.contentHeight, display.contentWidth + 500, 150 ) floor:setFillColor( 0.3137254901960784, 0.592156862745098, 0.23529411764705882 ) floor.type="floor" physics.addBody( floor, "static") end function scene:show( event ) if event.phase == "did" then timer.performWithDelay( 3000, spawnRects, -1) floor:addEventListener( "collision", onCollision ) end end function scene:hide( event ) -- body end function scene:destroy( event ) -- body end scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) return scene

it would be greatly appreciated if you could kindly comment on this behavior.

Regards,

Ack… deleted original post

 

Well, I had a bunch of advice, but let me just summarize this time around:

  • Table listeners better than function listeners.
  • display.remove() safer than obj:removeSelf()
  • Don’t make file-level (or global) references to objects unless you absolutely need to.
  • Insert objects into composer group or no purpose in using composer.
  • Let composer clean up your objects.  Its safer.
  • “kinematic”  not “kenematic”, but wrong type anyways.  Kinematic bodies don’t respond to gravity or forces.  I suggest “dynamic”

Sorry for being ‘terse’.  I just goofed and deleted a much nicer and verbose response…sigh.

Here is a reworked version of your code:

-- ============================================================= -- Nicer Composer Framework -- ============================================================= -- -- ============================================================= local composer = require( "composer" ) local scene = composer.newScene() local physics = require("physics") physics.start( ) ---------------------------------------------------------------------- -- LOCALS -- ---------------------------------------------------------------------- -- Variables local spawnTimer -- Forward Declarations (allows you to reference functions before defining them) local rectTap local spawnRects local onCollision ---------------------------------------------------------------------- -- Scene Methods ---------------------------------------------------------------------- ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:create( event ) local screenGroup = self.view local floor = display.newRect( screenGroup, display.contentWidth/2, display.contentHeight, display.contentWidth + 500, 150 ) floor:setFillColor( 0.3137254901960784, 0.592156862745098, 0.23529411764705882 ) floor.type="floor" physics.addBody( floor, "static") end ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:willEnter( event ) local screenGroup = self.view end ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:didEnter( event ) local screenGroup = self.view -- Used a closure to call the function and pass in screenGroup spawnTimer = timer.performWithDelay( 3000, function() spawnRects(screenGroup) end , -1) end ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:willExit( event ) end ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:didExit( event ) end ---------------------------------------------------------------------- ---------------------------------------------------------------------- function scene:destroy( event ) local screenGroup = self.view -- Stop that timer timer.cancel( spawnTimer ) spawnTimer = nil end ---------------------------------------------------------------------- -- FUNCTION/CALLBACK DEFINITIONS -- ---------------------------------------------------------------------- rectTap = function ( self, event ) transition.cancel( self.myTransition ) physics.addBody( self, "dynamic", {density=1.0, friction=0.2, bounce=0 } ) self:applyForce( 0, 10, self.x, self.x ) -- Note: I put the collision listener on the block instead tmp.collision = onCollision tmp:addEventListener( "collision") end spawnRects = function( group ) local tmp = display.newRect( group, 0, 0, 40, 40 ) tmp.id = numRects tmp.type = "rect" tmp.x = math.random(display.contentWidth + 50, display.contentWidth + 100 ) tmp.y = math.random( 70,100) tmp.myTransition = tmp.transition = transition.to( rects[numRects], {time=9000, x = -rects[numRects].width } ) tmp.tap = rectTap tmp:addEventListener( "tap", tmp ) end onCollision = function ( self, event ) print("onCollision()") display.remove(self) end --------------------------------------------------------------------------------- -- Scene Dispatch Events, Etc. - Generally Do Not Touch Below This Line --------------------------------------------------------------------------------- function scene:show( event ) local screenGroup = self.view local willDid = event.phase if( willDid == "will" ) then self:willEnter( event ) elseif( willDid == "did" ) then self:didEnter( event ) end end function scene:hide( event ) local screenGroup = self.view local willDid = event.phase if( willDid == "will" ) then self:willExit( event ) elseif( willDid == "did" ) then self:didExit( event ) end end scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) --------------------------------------------------------------------------------- return scene

Thanks a lot  @roaminggamer for your time and such a detailed reply.  

@roaminggamer, a little late but here are my findings (confusions) about the code (bear with me as I am new to corona and lua)

1. why in scene:create() and other listeners its local screenGroup and in show screenGroup (without local)

  1. I am getting an error at line ’ tmp.addEventListener( “tap”, tmp ) as attempt to call method respondsToEvent nil value. Additionally what is the difference between tmp:addEventListener and tmp.addEventListener (. and :  .)

  2. If I modify the logic to call the scene:destroy  by adding one global variable ‘count’ and then changing the onCollision as 

    onCollision = function ( self, event ) print(“onCollision()”) count = count + 1 display.remove(self) if count == 3 then composer.removeScene(“scene6”) end end

 I get an error when the scene is destroyed

bad argument to newRect, proxy expected got nil at line ‘local tmp = display.newRect( group, 0, 0, 40, 40 )’

Appreciate your help

Regards,

  1.  Are you looking at the code in this post (i.e. not attached to an e-mail as an update notice).  Always examine the code from the blog.  Why?  Because when you get a notification, it gives your the code/comments from the post, but not subsequent fixes.

I tend to make typos, see them, re-edit the post, and re-post.  You won’t see any of these corrections unless you look at the blog on the forums.

That said, I should have put a ‘local’ in front of each definition of ‘screenGroup’.

  1. I don’t use tap myself, as I prefer touch.  Typo: “tmp.addEventListener( “tap”, tmp )”  I should have typed “tmp:addEventListener( “tap”, tmp )”

I’ll fix that now.

  1.  You need to cancel the spawn timer first or it will try to add to a non-existent group:

    – Stop that timer timer.cancel( spawnTimer ) spawnTimer = nil – Now remove the scene composer.removeScene( “scene6” )