Deleting Tables

Is it the programmers responsibility to to release any memory used when exiting a program or does Codea (or Lua) do this for you?

As an example, I have a project that uses a table that gets fairly lengthy. I do nothing to free up the memory used when I exit the project and go back to the code. Is that sloppy memory-management on my part and is that why Codea crashes every now and again?

I have added a bit of code that nils the table in question then calls close() after I tap the screen rather then simply tapping the “back” arrow. Codea seems more stable with this addition. I may have answered my question but would appreciate any thoughts.

Thanks.

@Scotty no that’s not your fault, Lua uses a garbage collector and should free memory itself

It’s possible that we have a memory leak in Codea that can be triggered by putting lots of objects into Lua tables and then closing the project doesn’t free those (i.e., Lua frees its side, but there is some sort of cycle in the Codea side)

If you have a simple example of this code that causes the instability I would be keen to try it and put it through memory leak analysis

Let me start off by saying that I have a 3rd gen iPad running iOS S/W version 9.3.5. The OS has been at that level since Nov '17. So that may have something to do with what’s going on. At the very least help you assign a priority to this question. I don’t want to waste anyone’s time.

Thanks for looking into this issue.

-- StarField V3 -- Inspired by Codea's example project "Bit Invaders"

--[[ GOAL: Display stars radiating from screen center. Think Star Trek. Rather than lines falling from the top of the screen I wanted lines (stars) radiating from screen center. An eazy project that proved a bit more difficult.

Stars appear tapered to give a bit of perspective. Speed and size vary as well.

I have simplified the code taken from Bit Invaders just to make it easier to follow.

See StarField:draw() for handling of table at program exit.
--]]



displayMode(FULLSCREEN)
function setup()

    
    -- Screen center
    X = WIDTH/2
    Y = HEIGHT/2
    
    SF  = StarField()
    
    testing    = 1 -- (0=no, 1=yes)
    
    blueColor  = color(0, 0, 255, 255)
    redColor   = color(255, 0, 0, 255)
    whiteColor = color(255, 255, 255, 255)
        
end



-- This function gets called once every frame
function draw()
    background(0, 0, 0, 255)
    
    SF:update()
    SF:draw()
    
end




function touched(touch)
    if  touch.state == BEGAN then
        tapCT = touch.tapCount
    end
end




----------------------------------------------
-- A Single Star (Produce a single star)
----------------------------------------------
Star = class()

function Star:init(vel, angle, inc)
    self.velocity = vel
    self.angle    = angle
    self.inc      = inc
    self.position = vec2(0,0)

end



function Star:update()
    self.position.y = self.position.y + self.velocity*.2
    self.inc = self.inc + ((self.velocity*self.velocity)*.005)
end



function Star:draw()
    p = self.position -- this is a vec2() type
    taper = self.inc * .005 -- gives a slight taper to the star's "trail"
    coma  = self.inc * .04  -- the size of the star

    pushStyle()
    fontSize(12)
    fill(blueColor)
    stroke(blueColor)
    strokeWidth(self.inc*.015) -- Stars "closer" to viewer are thicker...
    lineCapMode(ROUND)
    pushMatrix()
        rotate(self.angle)
        -- The extra line gives a tapered appearance to the star's trail.
        line(p.x-taper, p.y+self.inc, p.x, p.y)
        line(p.x+taper, p.y+self.inc, p.x, p.y)
        ellipse(p.x,    p.y+self.inc, coma) -- ...and their "coma" appears larger.
    popMatrix()
    popStyle()
end



function Star:shouldCull()
    -- Check if beyond the screen by a certain amount. Mark for deletion if so.
    if self.position.y > WIDTH*.65 then
        return true        
    end 
    return false
end




----------------------------------------------
-- StarField (Displays all stars in StarField)
----------------------------------------------
StarField = class()

function StarField:init()
    
    self.minSpeed =  5
    self.maxSpeed = 25
    self.angle    =  0
    self.lineTab  = {}
    
end




function StarField:updateAndCull()
    for i,v in ipairs(self.lineTab) do
        if v:shouldCull() then
            table.remove(self.lineTab, i)
        else
            v:update()
        end
    end
    
end




function StarField:update()
    -- Just do this once so no need for a for statement.
    vel   = math.random(self.minSpeed, self.maxSpeed) -- velocity
    angle = math.random(-180,180)
    inc   = 0
    maxVolume = 125  -- Put a limit on the number of stars created.
    -- Set to a very high number will make cull decision in Star:shouldCull()

    --[[ A single star is written to a table with a call to Star() within the table.insert call. However, putting a limit on the number of stars created will reduce some of the "herky-jerkyness" when managing a large volumes. --]]
    if #self.lineTab < maxVolume then
        table.insert(self.lineTab, Star(vel, angle, inc))
    end
    
    self:updateAndCull()
    
end




function StarField:draw()

    pushStyle()
    pushMatrix()
        translate(WIDTH/2,HEIGHT/2)
        for i,v in ipairs(self.lineTab) do
            v:draw()
        end
    popMatrix()
    popStyle()

    -- Show line-table volume as a bar-graph.
    if testing == 1 then  self:lineTableVolume() end

    -- Blast table using "nil" then return to code.
    -- I'm not positive Codea cleans up after itself. That may be my responsibility.
    -- Comment next line out and it tends to crash more frequently.
    if tapCT == 4 then self.lineTab = nil close() end 
    
end




function StarField:lineTableVolume()
-- Just a bar graph that shows the number of lines in the line table.
    pushStyle()
    just = 15 -- justification
    pushMatrix()
        translate(100,75)
        fill(255, 200, 0, 255)
        textMode(CENTER)
        text("Line-Table Volume",0,-20)
        lineCapMode(SQUARE)
        strokeWidth(10)
        stroke(255,200,0,255)
        line(0,0,0,#self.lineTab)
        textMode(CORNER)
        text(#self.lineTab, -just*3,#self.lineTab)
        stroke(0, 0, 0, 255)
        strokeWidth(2)
        for i=0,650,50 do
            if #self.lineTab > i then text(i, just, i-10) line(-5, i, 5, i) end
        end
    popMatrix()
    popStyle()
end

@Scotty Nice program. Maybe you can alter the color instead of all blue. When I watched this it reminded me of some code I wrote awhile ago.

https://codea.io/talk/discussion/5230/moving-star-background

@Simeon When I ran Scottys code, Codea crashed when I exited the program. It didn’t do it every time though. I’m on current everything. I also noticed that Codea would crash sometimes when I exit some of my programs. Not sure if I mentioned that in the past or not.

PS. I notice it more when I’m doing testing where I’m running code, exiting the code, making changes, running, etc. There’s no specific cause that I notice, it just happens every now and then.

PS. I tried running/stopping Scotty’s program 25-30 times and Codea didn’t crash for those. It just crashed that one time when I first tried it.

@Scotty - neat demo, really brings back early Start Trek hyperspace. On the memory front and crashes I have had occasionally encountered it - must admit it is usually my poor programming that has led to it. If I encounter it I generally put a garbage collection in the draw() function which usually corrects it.

@dave1707 @Simeon - I have also had spontaneous crashes of Codea when I have been exiting a running project but I haven’t been able to tie it to any feature as yet. Running in latest everything , as @dave1707, on iPad Pro 9.7”.

To all: Thanks for the positive comments. Again, my inspiration came from Bit Invaders. That was the first suggestion when I went Googling for answers at the start of all this.

@dave1707 I’ll play with the colors to lend more interest. I know full-on blue is hard to see on a dark bg.

@Bri_G I’ll see what if putting garbage collection in draw() helps me. Right now I’m blasting the table in Starfield:draw() and that greatly helped.

And @Simeon, like dave1707, I also have other projects that crash every now and then. Glad to hear it’s not just me. Unlike Dave, I’m not running anything that’s current.

Added a color palette for randomly selecting colors for each star.

-- StarField V3 -- Inspired by Codea's example project "Bit Invaders"

--[[ GOAL: Display stars radiating from screen center. Think Star Trek. Rather than lines falling from the top of the screen I wanted lines (stars) radiating from screen center. What seemed like an eazy project at first proved a bit more difficult. I have a knack for pounding square pegs into round holes, but I learned stuff along the way, so that's all that matters.

Stars appear tapered to give a bit of perspective. Speed and size vary as well based on their appearent "closeness" to the viewer.

I have simplified the code taken from Bit Invaders just to make it easier to follow. 

See StarField:draw() for handling of table at program exit.
NOTE: Tap screen 4 times to close and return to source code.
--]]



displayMode(FULLSCREEN_NO_BUTTONS)
function setup()

    projName = "StarField"
    version  = "V3"
    vercolor = color(255, 0, 0, 50)
    print("Hello, StarField! (version "..version..")")   
    
    -- Screen center
    X = WIDTH/2
    Y = HEIGHT/2
    
    SF  = StarField()
    
    testing    = 1 -- (0=no, 1=yes) This display the line table volume bargraph.
    
    blueColor  = color(0, 0, 255, 255)
    redColor   = color(255, 0, 0, 255)
    whiteColor = color(255, 255, 255, 255)
    bgColor    = color(25, 25, 25, 255)

    colorR = color(255, 0, 0, 255)
    colorO = color(255, 100, 50, 255)
    colorY = color(255, 200, 0, 255)
    colorG = color(0, 255, 0, 255)
    colorB = color(0, 0, 255, 255)
    colorI = color(40, 0, 100, 255)   -- too dark for this project
    colorV = color(50, 0, 60, 255)    -- too dark for this project
    
    colorPalette = {colorR,colorO,colorY, colorG, colorB,colorI,colorV}
            
end



-- This function gets called once every frame
function draw()
    background(bgColor)
    --VersionStamper(projName,version,vercolor)
    
    SF:update()
    SF:draw()

end




function touched(touch)
    if  touch.state == BEGAN then
        tapCT = touch.tapCount
    end
end




----------------------------------------------
-- A Single Star (Produce a single star)
----------------------------------------------
Star = class()

function Star:init(vel, angle, inc, starColor)
    self.velocity  = vel
    self.angle     = angle
    self.inc       = inc
    self.starColor = starColor

    self.position  = vec2(0, math.random(0,100)) -- a positive X-value is interesting

end



function Star:update()
    self.position.y = self.position.y + self.velocity*.2
    self.inc = self.inc + ((self.velocity*self.velocity)*.005)
end



function Star:draw()
    p     = self.position -- this is a vec2() type
    taper = self.inc * .005 -- gives a slight taper to the star's "trail"
    coma  = self.inc * .04  -- the size of the star

    pushStyle()
    fontSize(12)
    
    fill(blueColor)
    stroke(blueColor)
    
    fill(self.starColor)
    stroke(self.starColor)
    
    strokeWidth(self.inc*.015) -- Stars "closer" to viewer are thicker...
    lineCapMode(ROUND)
    pushMatrix()
        rotate(self.angle)
        -- The extra line gives a tapered appearance to the star's trail.
        line(p.x-taper, p.y+self.inc, p.x, p.y)
        line(p.x+taper, p.y+self.inc, p.x, p.y)
        ellipse(p.x,    p.y+self.inc, coma) -- ...and their "coma" appears larger.
    popMatrix()
    popStyle()
end



function Star:shouldCull()
    -- Check if beyond the screen by a certain amount. Mark for deletion if so.
    if self.position.y > WIDTH*.65 then
        return true        
    end 
    return false
end




----------------------------------------------
-- StarField (Displays all stars in StarField)
----------------------------------------------
StarField = class()

function StarField:init()
    
    self.minSpeed = 10
    self.maxSpeed = 25
    self.angle    =  0
    self.lineTab  = {}
    
end




function StarField:updateAndCull()
    for i,v in ipairs(self.lineTab) do
        if v:shouldCull() then
            table.remove(self.lineTab, i)
        else
            v:update()
        end
    end
    
end




function StarField:update()
    -- Just do this once so no need for a for statement.
    vel   = math.random(self.minSpeed, self.maxSpeed) -- velocity
    angle = math.random(-180,180)
    inc   = 0
    
    -- Star colors
    for i=1, 5 do -- Use this for selecting from setup()-defined color palette.
        starColor = colorPalette[math.random(1,5)]
    end
    --[[ -- Use this for stars of random color.
    starColor = color(
        math.random(100,250),
        math.random(100,250),
        math.random(100,250),
        255)
      --]]
    --starColor = blueColor -- Use this if monochrome stars are desired.

    maxVolume = 125 -- Put a limit on the number of stars created.


--[[
A single star is written to a table with a call to Star() within the table.insert call. However, putting a reasonable limit on the number of stars created will reduce some of the "herky-jerkyness" when managing a large volumes.
--]]
    if #self.lineTab < maxVolume then
        -- Every star created gets a unique value
        table.insert(self.lineTab, Star(vel, angle, inc, starColor))
    end
    
    self:updateAndCull()
end




function StarField:draw()

    pushStyle()
    pushMatrix()
        translate(WIDTH/2,HEIGHT/2)
        for i,v in ipairs(self.lineTab) do
            v:draw()
        end
    popMatrix()
    popStyle()

    -- Show line-table volume as a bar-graph.
    if testing == 1 then  self:lineTableVolume() end

    -- Blast table using "nil" then return to code.
    if tapCT == 4 then self.lineTab = nil close() end     

end




function StarField:lineTableVolume() -- by Scotty
-- Just a bar graph that shows the number of lines in the line table.
    pushStyle()
    just = 15 -- justification
    pushMatrix()
        translate(100,75)
        fill(colorY)
        textMode(CENTER)
        text("Line-Table Volume",0,-20)
        lineCapMode(SQUARE)
        strokeWidth(10)
        stroke(colorY)
        line(0,0,0,#self.lineTab)
        textMode(CORNER)
        text(#self.lineTab, -just*3,#self.lineTab)
        stroke(bgColor)
        strokeWidth(2)
        for i=0,650,50 do
            if #self.lineTab > i then text(i, just, i-10) line(-5, i, 5, i) end
        end
    popMatrix()
    popStyle()
end

@Scotty very cool. One thing to watch is the approach of deleting table elements in the updateandcull function which could lead to undesirable outcomes if applied in different circumstances. You should be ok here as it is called each draw cycle, but below is an example where removing an element while updating might be bad. Tap screen to remove the 4th element in the table - note one element does not update.

-- Table Deletion

-- Use this function to perform your initial setup
function setup()
    currentchar=65
    tab={}
    for i=1,10 do
        table.insert(tab,{a=i,b=string.char(currentchar)})
    end
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color
    background(40, 40, 50)
    
    for i,t in pairs(tab) do
        text(t.a.." "..t.b,100,i*20+100)
    end
end

function touched(t)
    if t.state==ENDED then
        currentchar = currentchar + 1
        for i,t in pairs(tab) do
            if i==4 then
                table.remove(tab,i)
                i=i-1
            else
                t.b=string.char(currentchar)
            end
        end
    end
end

Basically you shouldn’t delete table elements going from the beginning to the end. You should delete elements going from the end to the beginning. In the above example, when your index reaches number 4 and you delete delete element number 4, all the elements above it in the table move down one position. The index is then incremented to 5 which is now the original element number 6 because it was moved down. The original element number 5 was skipped because it’s now in position number 4. By deleting in the reverse order, none of the lower positions change because nothing has been done to them yet.

@West , thank you for the interesting program. If I understand you correctly I could run into trouble by deleting elements “out of order”. In my program, since my “stars” travel as different speeds, how I decide to delete them (when the stars travel beyond a certain point) means that they are removed from the table “out of order” compared to how they were created. And that might cause problems in certain cases because, as @dave1707 mentioned, everything coming after it gets moved down.

That may explain an interesting thing that happened. I first tried to blast the table be removing it’s last element then closing and returning to the code. I had misread the syntax of table.remove. One by one the stars disappeared with each refreash. It did so in slow motion. As interesting as it was to watch it was clearly not what I wanted. And that dring me to my last question.

Regarding my program, my goal was to blast the table out of existence in one shot. In Starfield:draw() I set self.lineTab to “nil” when the user taps the screen four times. The table is blasted out of existence and the user is then returned to the source code. Adding that bit of code greatly reduced the frequency of crashes which is how this whole thread got started. Is that an acceptable way of dealing with a table upon exiting?

@Scotty Whenever I want to clear a table, I just use {} as in self.lineTab = {} .

Oh, yeah. Simple enough…Thanks.

@Scotty yes that’s right. @dave1707 explained it better but hopefully the code illustrates the point

Thanks, I always appreciate the help.

@dave1707 - since you are on the subject - how do you clear tables which enclose table subdivisions - created like:


instrument = { wood = {},
                       string = {},
                       percussion = {}
                     }

Am likely to use this in the near future and wonder if you can reset say string without affecting the rest - or have you to reconstruct the whole table set without adding entries to string?

@Bri_G To clear the string table without the other 2 tables, I would do instrument.string={} . Or instrument={} would clear everything.

@dave1707 - thanks for the confirmation, that’s just what I expected. Had an issue with something similar and opted for separate parent tables which worked well for me. But planning to go back and resolve the parent child tables I set up initially - just to tidy up from my own point of view.