Code problem involving tables

Here’s a snippet of my code that isolates the problem. Issue is, when two or more circles are added to the table, when one is destroyed (table.remove) the project spits out an error. Any help is appreciated, thanks!


--# Main

function setup()
    displayMode(FULLSCREEN)
    st = SmokeTest()
end


function draw()
    st:draw()
end

function touched(touch)
    st:touched(touch)
end

--# Smoke
Smoke = class()

function Smoke:init(killDistance)
    self.particles = {}
    self.kill = killDistance
end

function Smoke:addParticles(position, direction)
    -- 1 = color, 2 = direction, 3 = start position, 4 = current positiom
    local pos = table.maxn(self.particles) +1
    self.particles[pos] = {}
    self.particles[pos][1] = vec4(255,145,0,255)
    self.particles[pos][2] = direction
    self.particles[pos][3] = position
    self.particles[pos][4] = position
end

function Smoke:draw()
    for p = 1,  table.maxn(self.particles) do
    if self.particles[p][4]:dist(self.particles[p][3]) < self.kill then
    
    fill(255, 115, 0, 255)
    ellipse(self.particles[p][4].x, self.particles[p][4].y, WIDTH/10)   
    self.particles[p][4] = self.particles[p][4] + self.particles[p][2] 
    else
    print(self.particles[p][4]:dist(self.particles[p][3]))
    print(table.maxn(self.particles).."s2")
    table.remove(self.particles, p)   
    print(table.maxn(self.particles).."s1")       

    end
    end
end

function Smoke:touched(touch)
    -- Codea does not automatically call this method
end

--# SmokeTest
--For testing and demonstration of smoke class. Remove in finished project!
SmokeTest = class()

function SmokeTest:init()
self.delay = 1
self.smoke = Smoke(300)
end

function SmokeTest:draw()
background(0, 149, 255, 255)
self.smoke:draw()
end

function SmokeTest:touched(touch)
    if touch.state == BEGAN or MOVING then
    if self.delay == 5 then
        self.delay = 1
        self.smoke:addParticles(vec2(CurrentTouch.x, CurrentTouch.y), vec2(2,0))
    else
       self.delay = self.delay + 1 
    end
    end
end

you’re removing the item from the table in the middle of a for loop. So when it gets to the last entry in the loop it finds there’s nothing there. You either need to use an iterator like ipairs (which will request the next entry in the table if it exists each run through the loop) or loop backwards. Also you can use # instead of table.maxn. Try this in draw:
for p = #self.particles, 1 , -1 do

Thank you! I was thinking about this completely wrong, figuring the oldest entry was the highest index in the table. Thanks!

It depends on how you add things to the table. table.insert(myTable, value) defaults to adding items at the position #myTable+1, it’s the same as myTable[#myTable+1]=value. But you could insert things at the front of the table by specifying position 1, table.insert(myTable, 1, value), then the oldest entry would be highest. Either way though, when you table.remove all items above the one deleted move down one position, and the table length decreases by one. But in your original code, you’ve already given the for loop an instruction based on the length of the table before you removed an element, so the loop goes past the end of the table and crashes. By the way, there is a dedicated color(r,g,b,a) wrapper, don’t use a vec4

If I may add my 2c worth of code change suggestions, here’s a rewrite. Not sure I didn’t introduce bugs, but just wanted to share how I would have written it, with some comments.

--# Main

function setup()
    displayMode(FULLSCREEN)
    st = SmokeTest()
end

function draw()
    st:draw()
end

function touched(touch)
    st:touched(touch)
end

--# Particle
---- Creating a class for the particle doesn't cost much, and allows to
---- really separate things that only depend on the particle itself
---- from more global things.
Particle = class()

function Particle:init(col,direction,position)
    self.col = col
    self.direction = direction
    self.start = position
    self.current = position
    self.alive = true -- make the particle aware of whether it is still alive or not
en

---- this may appear in different places, so why not add it as a function on particle?
function Particle:distance()
    return self.current:dist(self.start)    
end

---- having the draw() function on the particle instead of inside the
---- smoke class will allow you to create particles outside of the
---- smoke class and display them, which may make debugging easier
---- (for instance, if the 'touch' features seam tricky, you can just manually
---- create a Particle using hardcoded values)
function Particle:draw()
    print("Drawing")
    if self.alive then -- only draw if alive
        pushStyle()        --- better to pushstyle before changing fill
        fill(255, 115, 0, 255)
        ellipse(self.current.x, self.current.y, WIDTH/10)   
        self.current = self.current + self.direction
        popStyle()         --- and popstyle when you are done so you revert to the previous fill color
    end
end

--# Smoke
Smoke = class()

function Smoke:init(killDistance)
    self.particles = {}
    self.kill = killDistance
end

function Smoke:addParticles(position, direction)
    -- not the cleanest, but a standard pattern to add stuff to a table is t[#t+1]=v
    self.particles[#self.particles+1] = Particle(vec4(255,145,0,255),direction,position)
end

function Smoke:draw()
    for i,p in ipairs(self.particles) do
        if p ~= nil then
             if p.alive and (p.distance() >= self.kill) then
                  print(p.distance())
                  print(#self.particles.."s2")
                  table.remove(self.particles, i)   
                  print(#self.particles.."s1")       
             end
             p:draw()        
    end
end

--# SmokeTest
--For testing and demonstration of smoke class. Remove in finished project!
SmokeTest = class()

function SmokeTest:init()
    self.delay = 1
    self.smoke = Smoke(300)
end

function SmokeTest:draw()
    background(0, 149, 255, 255)
    self.smoke:draw()
end

function SmokeTest:touched(touch)
    if touch.state == BEGAN or MOVING then
        if self.delay == 5 then
            self.delay = 1
            self.smoke:addParticles(vec2(CurrentTouch.x, CurrentTouch.y), vec2(2,0))
        else
           self.delay = self.delay + 1 
        end
    end
end

@joelhoro - very helpful, thank you. Welcome to the forum! :-h

Thanks. I’ll put some of this into my code. It’s quite a bit different already than the version I posted earlier, but lots of these ideas will still work. And welcome to the forum!

@joelhoro welcome to the forum :slight_smile:

I decided to give your code a try, and took some time to get the errors out of it (yes, we all do that sometimes, coding something and not testing it xd)

  1. the Particle:init isn’t completely closed (line 28) there’s ‘en’ instead of ‘end’

  2. the Smoke:draw has 1 end short (if looking at the format, it would be the “if p ~= nil” one that isn’t closed)

  3. in the Smoke:draw, you call the function “p.distance” 2 times, it should be “p:distance” to make it work

Note that I’m not trying to break anyone down, but just trying to help :slight_smile:

Apologies for the few typos. I first typed this in Codea then copy pasted to my browser and that’s when I added a few remarks and more changes. Hopefully you didn’t waste too much time fixing these, but what I wanted to get across were the ideas of factoring the code in smaller pieces.

And thx all for the warm welcome!