0
votes

I'm new to corona and was trying to do some kind of object pooling on moving platforms. When they exit the screen theyre moved from visibleBlocks to blocks. When I log counts of both my tables the numbers dont match the way they should.. and there are long gaps in my blocks appearing on screen.

My blocks generation:

local color = 'red'
for i = 1, 10 do 
    local block = display.newRect( 0, 0, 60, 2 )
    block.index = i
    block.name = 'block'
    block.isVisible = false
    physics.addBody( block, 'static' )
    blocks[i] = block
    sceneGroup:insert( block )

    block.color = color

    color = switchColor(block)  
end

code detecting when blocks left the screen

function update()

    for k, block in pairs(visibleBlocks) do
        block.y = block.y - 1
        if block.y < 0 then
            removeBlock(block)
        end
    end

removeblock code

function removeBlock(block)

    block.isVisible = false
    block.isBodyActive = false
    blocks[block.index] = block
    visibleBlocks[block.index] = nil


    print (' ')
    print( 'blocks: ' .. #blocks)
    print( 'visible blocks: ' .. #visibleBlocks )

end

addblock which is executed on a timer

function addBlock()
    local block = table.remove( blocks )
    if block ~= nil then
        block.isVisible = true
        block.isBodyActive = true
        visibleBlocks[block.index] = block

        block.x = math.random( 
            block.contentWidth/2 + 20, 
            display.contentWidth - block.contentWidth/2 - 20 
        )
        block.y = display.contentHeight

    end
end

my console output looks like this:

Oct 23 08:50:16.281: blocks: 0
Oct 23 08:50:16.281: visible blocks: 9
Oct 23 08:50:17.289:  
Oct 23 08:50:17.290: blocks: 0
Oct 23 08:50:17.290: visible blocks: 8
Oct 23 08:50:18.329:  
Oct 23 08:50:18.329: blocks: 10
Oct 23 08:50:18.329: visible blocks: 7
Oct 23 08:50:19.353:  
Oct 23 08:50:19.354: blocks: 9
Oct 23 08:50:19.354: visible blocks: 6
Oct 23 08:50:20.313:  
Oct 23 08:50:20.314: blocks: 8
Oct 23 08:50:20.314: visible blocks: 5
Oct 23 08:50:21.339:  
Oct 23 08:50:21.340: blocks: 0
Oct 23 08:50:21.340: visible blocks: 10
Oct 23 08:50:22.376:  
Oct 23 08:50:22.376: blocks: 7
Oct 23 08:50:22.376: visible blocks: 10
Oct 23 08:50:23.390:  
Oct 23 08:50:23.390: blocks: 6
Oct 23 08:50:23.390: visible blocks: 10
Oct 23 08:50:24.392:  
Oct 23 08:50:24.393: blocks: 5
Oct 23 08:50:24.393: visible blocks: 10
Oct 23 08:50:25.457:  
Oct 23 08:50:25.457: blocks: 4
Oct 23 08:50:25.458: visible blocks: 10

these numbers should always add up to 10 right? something isnt quite right here

1
I don't fully follow the logic here but you are almost certainly creating "holes" in your tables by recycling indices between them like that. And # is only defined on tables without holes. - Etan Reisner

1 Answers

0
votes

By saying visibleBlocks[block.index] = nil in removeBlock, you are creating holes in your "array", which means what # does is no longer clearly defined. As an example,

function footest( t )  print( ("%d,%s"):format( #t, tostring( t[2] ) ) )  end
footest { 1, nil, 3 }
--> 3,nil
footest { 1, [3] = 3 }
--> 1,nil
t = { 1, 2, 3 } ; t[2] = nil ; footest( t )
--> 3,nil
u = { 1, 2, 3 } ; u[1] = nil ; footest( u )
--> 3,2
u[2] = nil ; footest( u )
--> 3,nil
u[3] = nil ; footest( u )
--> 0,nil

(at least this is what currently happens on my machine… While you could look at the source and find out what will happen in all of these cases for the particular version you're using, the reference manual just says that what happens is undefined.)


So how to fix this?

One way of going about this would be to use table.remove instead of nil-ing fields. (table.remove( t, n ) will shift all elements "right of" n in the "array" one to the left to close the gap, that is for

t = { [1] = 1, [2] = 2, [3] = 3 }

saying

table.remove( t, 1 )

results in

t = { [1] = 2, [2] = 3, [3] = nil }

and not leaving a gap inside the array.) This works well when you depend on the order of things in the table/"array" but not on their absolute position.

Unfortunately, it seems you're relying on fixed positions for all blocks (block.index) – if so, this won't work. (If this does work and I'm just mis-interpreting your code, it's probably the simplest way to fix this.)


So a better way might be to assign false instead of nil. You'll have to adjust update (and maybe other code that I didn't see), but the changes are minor:

Your

function update()
    for k, block in pairs(visibleBlocks) do
        block.y = block.y - 1
        if block.y < 0 then
            removeBlock(block)
        end
    end
end

turns into

function update()
    for k, block in ipairs(visibleBlocks) do
        if block then
            block.y = block.y - 1
            if block.y < 0 then
                removeBlock(block)
            end
        end
    end
end

The first change – replacing pairs with ipairs – is something that you should do anyway whenever you're using a table as an array. (ipairs will stop at the first gap, which means if you accidentally create a hole, a lot of stuff will be missing and you get a pretty obvious error that's easier to debug than those minor glitches. Also, if your array contains holes, pairs may not iterate over everything in ascending order – try it with t = { 1, 2, 3, [9] = 9, [12] = 12 }, iterating with pairs may produce the order 1, 2, 3, 12, 9!)

The only other change is checking if block then …, because some blocks may be absent, as represented by false, and will not silently be skipped like when using nil and pairs.

The result will be good if you depend on position (and, implicitly, order) of elements in the table/"array", and the "is this actually a block?"-check isn't too bad.


A third option – if you're relying neither on the order nor on the absolute position of elements – would be not trying to use the table as an array. Just use each block as a key and assign a dummy value (commonly true) to its entry. (As a bonus, you don't need to remember an extra block.index – the block is the index!)

To add a block, just say blocks[block] = true, to remove, just say blocks[block] = nil. To iterate over all blocks,

for block in pairs( blocks ) do
    …
end

but if you need to count the blocks, you'll need a function like

function size( t )
    local count = 0
    for _ in pairs( t ) do  count = count + 1  end
    return count
end

This version will break horribly when you rely on the relative order as the drawing order or something like that: Whenever you insert an element, the order of all entries may change completely, which would result in blocks randomly popping above/below others. Compare:

t = { [5] = 1, [2] = 1, [8] = 1 }
for k in pairs( t ) do print( k ) end
--> 8, 5, 2
t[3] = 1
for k in pairs( t ) do print( k ) end
--> 8, 5, 2, 3
t[6] = 1
for k in pairs( t ) do print( k ) end
--> 2, 3, 5, 6, 8

(and you could get a completely different order if you run this.)

If this potential problem doesn't apply this may actually result in the simplest code overall (even though you'll probably have to restructure a bit more.)