How do I interpret this memory leak output?

I got a memory leak, and here is the print output.  I’m not sure how I should interpret it, and how I can use it to track down the problem source.  I’d greatly appreciate any help because I’m starting to get a sick feeling from this…  I ran the program between 3 storyboard scenes.  Memory usage is printed at the beginning of each enterscene.  Game runs smoothly, but after changing scenes multiple times, FPS drops dramatically.

from main.lua
 System Memory Used:    1.217    Mb
 Texture Memory Used:    11.534    Mb

from mainmenu.lua
 System Memory Used:    1.444    Mb      ***
 Texture Memory Used:    11.534    Mb

from level_select.lua
 System Memory Used:    1.824    Mb     *
 Texture Memory Used:    15.499    Mb

from maingame.lua
 System Memory Used:    2.295    Mb
 Texture Memory Used:    13.320    Mb

[game changes scene back to mainmenu.lua here]

from mainmenu.lua
System Memory Used:        1.878    Mb     ***
Texture Memory Used:    16.450    Mb

from level_select.lua
 System Memory Used:    2.234    Mb  *
 Texture Memory Used:    15.516    Mb

from maingame.lua
 System Memory Used:    2.251    Mb
 Texture Memory Used:    13.369    Mb


from mainmenu.lua
 System Memory Used:    2.050    Mb      ***
 Texture Memory Used:    16.450    Mb

from level_select.lua
 System Memory Used:    2.417    Mb  *
 Texture Memory Used:    15.499    Mb

from maingame.lua
 System Memory Used:    3.876    Mb
 Texture Memory Used:    13.353    Mb


from mainmenu.lua
 System Memory Used:    2.236    Mb      ***
 Texture Memory Used:    16.433    Mb

from level_select.lua
 System Memory Used:    2.585    Mb  *
 Texture Memory Used:    15.499    Mb

from maingame.lua
 System Memory Used:    3.970    Mb
 Texture Memory Used:    13.369    Mb
 

It looks like you have a Lua memory leak but not a texture memory leak.  That does help narrow it down a bit – it means you’re using storyboard correctly to clean up your display objects when you move between scenes.

As for what’s causing the Lua memory leak, it could be a number of things.  Since it seems to be happening in each of your scenes, if you could post any one of them, we may be able to spot it.

  • Andrew

Thank you Andrew!

I’m going to post the level_select.lua (it’s a tutorial I modified) since it is the shortest:

   

    local storyboard = require( “storyboard” )
    local scene = storyboard.newScene()
    local pg = require (“classes.proj_global”)
    local sg = require (“classes.scr_global”)
    local ggd = require (“classes.ggdata”)

    local easingx = require (“classes.easingx”)

    – local forward references should go here –

    local tHandle = { a = {}, b = {}}

    – local levels = – moved to proj_global.lua
    –     {
    –         1, 1,  --1 means level is open to be played (level.png)
    –         1, 2,  --2 means level is locked (locked.png)
    –         2, 2, – 3 means level is completed (greenchecked.png)
    –     }
     
    local images ={
        { getFile = pg.imgPath…“level.png”, types = “play” },
        { getFile = pg.imgPath…“lock.png”, types = “locked”},
        { getFile = pg.imgPath…“done.png”, types = “done”}
    }
     
    local levelImage = {}

    local function buttonHit(event)
        print( event.target.destination … ".lua, selected level: "…event.target.levelNum)
        pg.level = event.target.levelNum
        storyboard.gotoScene ( event.target.destination, {effect = “slideRight”} )
        return true
    end
         
    – Called when the scene’s view does not exist:
    function scene:createScene( event )
        local group = self.view

        local txtGroup, tablePlace, levelIndex, imagesId
        local xOffset = 55
        local yOffset = -30

        levelImage = {}

            local levelImage
            local col = 2
            local row = 3

            local ImgSize = 256

        local levelLabel = { “Winter”, “Spring”, “Summer”, “Fall”, “foo”, “bar”}

        local box1 = ggd:new (“lvl”) – load level settings file

        if box1.levels == nil then
            box1.levels = pg.levels
        else
            pg.levels = box1.levels
        end
        --box1:save()

        local levelIndex = 0

        levelImage = {}
        for i=1,row do
            for j=1,col do
                 tablePlace = i*5 + j
                 levelIndex = levelIndex + 1
                 print ("LEVEL INDEX: "… levelIndex)
                 imagesId = pg.levels[levelIndex]

                levelImage[#levelImage+1] = display.newImageRect (images[imagesId].getFile , ImgSize*.7, ImgSize*.7 )

                levelImg = levelImage[levelIndex]
                levelImg.x = (j*ImgSize) - xOffset
                levelImg.y = yOffset + (i*ImgSize)

                print (“ICON XY: " …  levelImg.x …”, " … levelImg.y)

                levelImg.alpha = 0
                levelImg.xScale = 2
                levelImg.yScale = 2
                levelImg.rotation = -155
                tHandle.a[levelIndex]  = transition.to (levelImg, { delay = levelIndex * 200, time = 2000, xScale = 1, yScale = 1, alpha = 1, rotation = 0, transition = easingx.easeOutElastic})
               group:insert(levelImg)
                 
                --leveltxt = display.newText("Level "…tostring(levelIndex), 0,0, “Helvetica”, 20)  --native.systemFont
                
                local leveltxt = display.newText(levelIndex … ". " …levelLabel[levelIndex], 0,0, “GillSans-Book”,
                    40)

                leveltxt.x = (j*ImgSize) - xOffset
                leveltxt.y = yOffset + 107 + (i*ImgSize)
                leveltxt:setTextColor (250, 255, 25)
               group:insert (leveltxt)

                leveltxt.alpha = 0
                tHandle.b[levelIndex] = transition.to (leveltxt, {delay = levelIndex * 250 + 500, time =500, alpha =1})
                 
                levelImg.destination = “maingame” --…tostring(levelIndex)
                levelImg.levelNum = levelIndex
                 
                if images[imagesId].types ~= “locked” then
                    levelImg:addEventListener(“tap”, buttonHit)
                    levelImg.isButton = true
                end
            end
         
        end
    end
     
    – Called immediately after scene has moved onscreen:
    function scene:enterScene( event )
    local group = self.view
     
    – INSERT code here (e.g. start timers, load audio, start listeners, etc.)
        printMemUse(“from level_select”)

    end
     
    – Called when scene is about to move offscreen:
    function scene:exitScene( event )
    local group = self.view
     
    – INSERT code here (e.g. stop timers, remove listeners, unload sounds, etc.)
    – Remove listeners attached to the Runtime, timers, transitions, audio tracks
    
        for i, j in pairs (tHandle.a) do
            transition.cancel (j)
        end

        for i, j in pairs (tHandle.b ) do 
            transition.cancel (j)
        end

        for i, j in pairs (levelImage) do
            if j.isButton~= nil then
                j:removeEventListener (“tap”, buttonHit)
            end
        end
    end
     
    – Called prior to the removal of scene’s “view” (display group)
    function scene:destroyScene( event )
    local group = self.view
     
    – INSERT code here (e.g. remove listeners, widgets, save state, etc.)
    – Remove listeners attached to the Runtime, timers, transitions, audio tracks
     
    end
     
    ---------------------------------------------------------------------------------
    – END OF YOUR IMPLEMENTATION
    ---------------------------------------------------------------------------------
     
    – “createScene” event is dispatched if scene’s view does not exist
    scene:addEventListener( “createScene”, scene )
     
    – “enterScene” event is dispatched whenever scene transition has finished
    scene:addEventListener( “enterScene”, scene )
     
    – “exitScene” event is dispatched before next scene’s transition begins
    scene:addEventListener( “exitScene”, scene )
     
    – “destroyScene” event is dispatched before view is unloaded, which can be
    – automatically unloaded in low memory situations, or explicitly via a call to
    – storyboard.purgeScene() or storyboard.removeScene().
    scene:addEventListener( “destroyScene”, scene )
     
    ---------------------------------------------------------------------------------
     
    return scene

I don’t see anything in this file that would cause a Lua memory leak.  Even if they’re larger, you can post the other files too.

One thing I did notice is that levelImg is accidentally being declared a global.  But that alone shouldn’t cause the memory leak.

Out of curiosity, are you using setting either (or both) of storyboard.disableAutoPurge and storyboard.purgeOnSceneChange to true?  (If you are, you’d probably be doing it in your main.lua.  It should be fine if you’re not, but I’d just like to know to help narrow down the issue.)

  • Andrew

I see this line:

>    storyboard.purgeOnSceneChange = true
 

on main.lua.  I don’t see anything else. 

I’m kinda suspectings this bit of code below:

<<<<

local fg = require “classes.fglobal”
local pg = require “classes.proj_global”
local ps = require “classes.lib_particle_candy”
local fx = require “classes.ekfxlib”
local tx = require “classes.ektx”
local ot = require “classes.objtype”
local sg = require “classes.scr_global”

local sp = {  }

local sp_mt = { __index = sp } 

    local physics = require “physics”
    local easingx = require “classes.easingx”

    sp.view = nil –  main display group

    local UP_SPAWN_LIMIT = 300
    local WIDTH = 50

sp.createView = function ()
   sp.view = display.newGroup ()
end

sp.removeView = function ()
    --self.stopped = true

    if sp.view == nil then
        return;
    end

    sp.view:removeSelf()   --!
    sp.view = nil
end

sp.new = function(name, objNum, objTypeA )  –  constructor

    local newObject = {
                myName = name or “unnamed”,
                stopped = false,
                unloading = false,
                objType = objTypeA or ot.CIRCLE,
                average = objNum or 3,
                allStuff = {},
        }

    return setmetatable (newObject, sp_mt)
end

function sp:status ()
    print ("objType "… self.objType)
    print ("average “… self.average)
    print (”#allStuff " … #self.allStuff)
end

------- Methods  -----

function sp:spawnStuff (sDelay)

    if self.stopped == true then return; end

    local pscale = math.random (3,6) / 10
    local pradius = 20
    
    local function onCollision( self, event )            
        – occurs, when objects collide with the obstacle    
        if event.phase == “began” then                    
            if event.other.myName == “sat1” then
               
                self.kill = true
                if (self.myName == ot.SPIKE1) or (self.myName == ot.SPIKE2) or (self.myName == ot.SPIKE3)  then
                    self.kill = false
                end
            elseif (event.other.myName == “player1”) then
                if (self.myName==ot.SPIKE1) or (self.myName==ot.SPIKE2) or (self.myName==ot.SPIKE3) or (self.myName == ot.HEART) then

                    self.kill = true
                end
            end    
        end
    end

    local function onObjShowComplete( target )   
–        if sp.stopped == true then return; end

        if target == nil then return; end
        if self.unloading then return; end
        if target.kill then return; end

        physics.addBody (target, {  density = 1.0, friction = 0.3, bounce = 0.2, radius = pradius * pscale} )

        target.collision = onCollision
        target:addEventListener (“collision”, target)

        --taret.myName = “obj” … math.random (100)
        local force = math.random( 1, 5 )        
        if math.random (2) == 1 then force = force * -.5; end
        target:applyLinearImpulse( force / 5, force, target.x, target.y )
        local torque = math.random (5, 150)
        if math.random (2) == 1 then torque = torque * -1; end
        target:applyTorque (torque)
    end

    – define needed variables    
    local x
    local y
    local size = 50
    local delay    = 50
    
    – get random values for the objects parameters

    x = math.random( sg.left + 100, sg.right - 100)
    y = math.random( 0, UP_SPAWN_LIMIT )     

–    dprint (pg.imgPath)
    if self.objType == ot.STAR1 then
        pradius = 32
        self.allStuff[#self.allStuff + 1] =
            display.newImageRect ( pg.imgPath … “mbo1” … “.png”, 64 *pscale, 64 *pscale)    
    
    elseif self.objType == ot.STAR2 then
        pradius = 32
        self.allStuff[#self.allStuff + 1] =
            display.newImageRect ( pg.imgPath … “mbo2” … “.png”, 64 *pscale, 64 *pscale)    
    elseif self.objType == ot.STAR3 then
        pradius = 32
        self.allStuff[#self.allStuff + 1] =
            display.newImageRect ( pg.imgPath … “mbo3” … “.png”, 64 *pscale, 64 *pscale)
    end

    print ("Menu–>objType "… self.objType)
    --print ("average “… self.average)
    --print (”#allStuff " … #self.allStuff)
    --print ("test # "… #self.allStuff)
    local stuff = self.allStuff [#self.allStuff]
    stuff.x = x
    stuff.y = y

    sp.view:insert (stuff)
    
    --stuff.linearDamping = 0.4
    --stuff.angularDamping = 0.6
    stuff.alpha = 0     
    stuff.xScale = 0.1
    stuff.yScale = 0.1
    stuff.rotation = math.random (360)
    stuff.myName = self.objType;  – … math.random (100)
    stuff.kill = false

    if fg.doDebug then
        stuff.tHandle = transition.to( stuff, { transition = easing.outExpo, time = 500, xScale = 1, yScale = 1, alpha = 1, delay = sDelay, onComplete = onObjShowComplete } )    
    else
        stuff.tHandle = transition.to( stuff, { transition = easingx.easeOutElastic, time = 500, xScale = 1, yScale = 1, alpha = 1, delay = sDelay, onComplete = onObjShowComplete } )    
    end    

end

function sp:removeAndAddStuff()
    
    if self.stopped == true then return; end

    local allStuff = self.allStuff

    for i=#allStuff, 1, -1 do
        --print (“y=”…oneStuff.y)
        if allStuff[i] then
            if (allStuff[i].y > sg.bottom + WIDTH ) or
                (allStuff[i].y < sg.top - WIDTH ) or
                (allStuff[i].x < sg.left - WIDTH ) or
                (allStuff[i].x > sg.right + WIDTH ) then
                    --oneStuff:removeSelf()
                    transition.cancel (allStuff[i].tHandle)

                    local oneStuff = allStuff[i]
                    display.remove (oneStuff)  – safest way?
                    --display.remove (allStuff[i])  – another way to remove object.
                    --allStuff[i]:removeSelf()  – ?!
                    oneStuff = nil
                    table.remove (allStuff,i)
            end
        end
    end

–    oneStuff = nil

    local j =  self.average - #self.allStuff
–    print (“j:”…j…"   #allStuff:"…#self.allStuff)

    if j > 0 then
        for k = 1, j do
            sp.spawnStuff (self, 0)
        end
    end

end

function sp:stop () – stop spawning
    self.stopped = true
end

function sp:start()
    self.stopped = false
end

function sp:unload () – stop everything and remove everything.
    self.stopped = true
    self.unloading = true
    print “sp: unload started…”

    for i = 1, #self.allStuff do
        if self.allStuff[i] then
            transition.cancel (self.allStuff[i].tHandle)
                    local oneStuff = table.remove (self.allStuff,i)

                    if oneStuff ~=nil then    
                        oneStuff:removeSelf()
                        oneStuff=nil
                    end
        end
    end
end

function sp:startCreatingStuff( )    
    – create the objects
    self.stopped = false
    for i = 1, self.average do
        sp.spawnStuff (self, 0 )–i*math.random (200,500))
    end    
end

return sp
 

It could be.  Do you ever call removeAndAddStuff?  If not, then that would be a source of a Lua memory leak.

  • Andrew

I call it on enterFrame.  … 

There’s nothing wrong with removeAndAddStuff() itself, then?

Can you give me an example of what kind of code can cause Lua memory leak?

Thanks for your help Andrew

I think I found the troubled area (menu system), if this is a memory leak.  It drops back momentarily, so I’m not sure.  Do I need to keep this running longer?

           from MAIN.lua
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    0.474    Mb
 ---->Texture Memory Used:    2.097    Mb
 ------------------------------------------
 

           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    0.584    Mb
 ---->Texture Memory Used:    2.097    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    0.670    Mb
 ---->Texture Memory Used:    6.357    Mb
 ------------------------------------------

           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.225    Mb
 ---->Texture Memory Used:    11.141    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    0.952    Mb
 ---->Texture Memory Used:    11.207    Mb
 ------------------------------------------

           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.527    Mb
 ---->Texture Memory Used:    11.796    Mb
 
           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.175    Mb
 ---->Texture Memory Used:    11.862    Mb
 ------------------------------------------

           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.930    Mb
 ---->Texture Memory Used:    12.452    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.462    Mb
 ---->Texture Memory Used:    12.517    Mb
 ------------------------------------------

           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    2.068    Mb
 ---->Texture Memory Used:    13.107    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.304    Mb
 ---->Texture Memory Used:    13.173    Mb
           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.845    Mb
 ---->Texture Memory Used:    13.763    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    2.357    Mb
 ---->Texture Memory Used:    13.828    Mb
           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.386    Mb                        <---- dropped back here
 ---->Texture Memory Used:    14.418    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.894    Mb
 ---->Texture Memory Used:    14.483    Mb
           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    2.459    Mb
 ---->Texture Memory Used:    15.073    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    2.971    Mb
 ---->Texture Memory Used:    15.139    Mb
           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.965    Mb
 ---->Texture Memory Used:    15.729    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    2.519    Mb
 ---->Texture Memory Used:    15.794    Mb

As long as it’s not continuously increasing, you should be fine.

By the way, make sure you call collectgarbage() immediately before printing out the memory usage.  This forces the Lua garbage collector to reclaim memory.  If you’re not doing that, then the Lua memory output you see will still be indicatively correct, but it’ll be harder to discern a pattern.

If you call collectgarbage() before printing the memory usage, then the pattern (if any) becomes much clearer.  What you don’t want to see is a clearly increasing pattern.  Jumps up and down are fine as long as it’s not overall trending upwards.

  • Andrew

I found that I forgot to call one of the clean up functions.  I also updated all “module package see all…” to “M = {}…” 

Anyways, thank you again Andrew.  I don’t think I could’ve done this if I felt that I was all alone and scared he he.

It looks like you have a Lua memory leak but not a texture memory leak.  That does help narrow it down a bit – it means you’re using storyboard correctly to clean up your display objects when you move between scenes.

As for what’s causing the Lua memory leak, it could be a number of things.  Since it seems to be happening in each of your scenes, if you could post any one of them, we may be able to spot it.

  • Andrew

Thank you Andrew!

I’m going to post the level_select.lua (it’s a tutorial I modified) since it is the shortest:

   

    local storyboard = require( “storyboard” )
    local scene = storyboard.newScene()
    local pg = require (“classes.proj_global”)
    local sg = require (“classes.scr_global”)
    local ggd = require (“classes.ggdata”)

    local easingx = require (“classes.easingx”)

    – local forward references should go here –

    local tHandle = { a = {}, b = {}}

    – local levels = – moved to proj_global.lua
    –     {
    –         1, 1,  --1 means level is open to be played (level.png)
    –         1, 2,  --2 means level is locked (locked.png)
    –         2, 2, – 3 means level is completed (greenchecked.png)
    –     }
     
    local images ={
        { getFile = pg.imgPath…“level.png”, types = “play” },
        { getFile = pg.imgPath…“lock.png”, types = “locked”},
        { getFile = pg.imgPath…“done.png”, types = “done”}
    }
     
    local levelImage = {}

    local function buttonHit(event)
        print( event.target.destination … ".lua, selected level: "…event.target.levelNum)
        pg.level = event.target.levelNum
        storyboard.gotoScene ( event.target.destination, {effect = “slideRight”} )
        return true
    end
         
    – Called when the scene’s view does not exist:
    function scene:createScene( event )
        local group = self.view

        local txtGroup, tablePlace, levelIndex, imagesId
        local xOffset = 55
        local yOffset = -30

        levelImage = {}

            local levelImage
            local col = 2
            local row = 3

            local ImgSize = 256

        local levelLabel = { “Winter”, “Spring”, “Summer”, “Fall”, “foo”, “bar”}

        local box1 = ggd:new (“lvl”) – load level settings file

        if box1.levels == nil then
            box1.levels = pg.levels
        else
            pg.levels = box1.levels
        end
        --box1:save()

        local levelIndex = 0

        levelImage = {}
        for i=1,row do
            for j=1,col do
                 tablePlace = i*5 + j
                 levelIndex = levelIndex + 1
                 print ("LEVEL INDEX: "… levelIndex)
                 imagesId = pg.levels[levelIndex]

                levelImage[#levelImage+1] = display.newImageRect (images[imagesId].getFile , ImgSize*.7, ImgSize*.7 )

                levelImg = levelImage[levelIndex]
                levelImg.x = (j*ImgSize) - xOffset
                levelImg.y = yOffset + (i*ImgSize)

                print (“ICON XY: " …  levelImg.x …”, " … levelImg.y)

                levelImg.alpha = 0
                levelImg.xScale = 2
                levelImg.yScale = 2
                levelImg.rotation = -155
                tHandle.a[levelIndex]  = transition.to (levelImg, { delay = levelIndex * 200, time = 2000, xScale = 1, yScale = 1, alpha = 1, rotation = 0, transition = easingx.easeOutElastic})
               group:insert(levelImg)
                 
                --leveltxt = display.newText("Level "…tostring(levelIndex), 0,0, “Helvetica”, 20)  --native.systemFont
                
                local leveltxt = display.newText(levelIndex … ". " …levelLabel[levelIndex], 0,0, “GillSans-Book”,
                    40)

                leveltxt.x = (j*ImgSize) - xOffset
                leveltxt.y = yOffset + 107 + (i*ImgSize)
                leveltxt:setTextColor (250, 255, 25)
               group:insert (leveltxt)

                leveltxt.alpha = 0
                tHandle.b[levelIndex] = transition.to (leveltxt, {delay = levelIndex * 250 + 500, time =500, alpha =1})
                 
                levelImg.destination = “maingame” --…tostring(levelIndex)
                levelImg.levelNum = levelIndex
                 
                if images[imagesId].types ~= “locked” then
                    levelImg:addEventListener(“tap”, buttonHit)
                    levelImg.isButton = true
                end
            end
         
        end
    end
     
    – Called immediately after scene has moved onscreen:
    function scene:enterScene( event )
    local group = self.view
     
    – INSERT code here (e.g. start timers, load audio, start listeners, etc.)
        printMemUse(“from level_select”)

    end
     
    – Called when scene is about to move offscreen:
    function scene:exitScene( event )
    local group = self.view
     
    – INSERT code here (e.g. stop timers, remove listeners, unload sounds, etc.)
    – Remove listeners attached to the Runtime, timers, transitions, audio tracks
    
        for i, j in pairs (tHandle.a) do
            transition.cancel (j)
        end

        for i, j in pairs (tHandle.b ) do 
            transition.cancel (j)
        end

        for i, j in pairs (levelImage) do
            if j.isButton~= nil then
                j:removeEventListener (“tap”, buttonHit)
            end
        end
    end
     
    – Called prior to the removal of scene’s “view” (display group)
    function scene:destroyScene( event )
    local group = self.view
     
    – INSERT code here (e.g. remove listeners, widgets, save state, etc.)
    – Remove listeners attached to the Runtime, timers, transitions, audio tracks
     
    end
     
    ---------------------------------------------------------------------------------
    – END OF YOUR IMPLEMENTATION
    ---------------------------------------------------------------------------------
     
    – “createScene” event is dispatched if scene’s view does not exist
    scene:addEventListener( “createScene”, scene )
     
    – “enterScene” event is dispatched whenever scene transition has finished
    scene:addEventListener( “enterScene”, scene )
     
    – “exitScene” event is dispatched before next scene’s transition begins
    scene:addEventListener( “exitScene”, scene )
     
    – “destroyScene” event is dispatched before view is unloaded, which can be
    – automatically unloaded in low memory situations, or explicitly via a call to
    – storyboard.purgeScene() or storyboard.removeScene().
    scene:addEventListener( “destroyScene”, scene )
     
    ---------------------------------------------------------------------------------
     
    return scene

I don’t see anything in this file that would cause a Lua memory leak.  Even if they’re larger, you can post the other files too.

One thing I did notice is that levelImg is accidentally being declared a global.  But that alone shouldn’t cause the memory leak.

Out of curiosity, are you using setting either (or both) of storyboard.disableAutoPurge and storyboard.purgeOnSceneChange to true?  (If you are, you’d probably be doing it in your main.lua.  It should be fine if you’re not, but I’d just like to know to help narrow down the issue.)

  • Andrew

I see this line:

>    storyboard.purgeOnSceneChange = true
 

on main.lua.  I don’t see anything else. 

I’m kinda suspectings this bit of code below:

<<<<

local fg = require “classes.fglobal”
local pg = require “classes.proj_global”
local ps = require “classes.lib_particle_candy”
local fx = require “classes.ekfxlib”
local tx = require “classes.ektx”
local ot = require “classes.objtype”
local sg = require “classes.scr_global”

local sp = {  }

local sp_mt = { __index = sp } 

    local physics = require “physics”
    local easingx = require “classes.easingx”

    sp.view = nil –  main display group

    local UP_SPAWN_LIMIT = 300
    local WIDTH = 50

sp.createView = function ()
   sp.view = display.newGroup ()
end

sp.removeView = function ()
    --self.stopped = true

    if sp.view == nil then
        return;
    end

    sp.view:removeSelf()   --!
    sp.view = nil
end

sp.new = function(name, objNum, objTypeA )  –  constructor

    local newObject = {
                myName = name or “unnamed”,
                stopped = false,
                unloading = false,
                objType = objTypeA or ot.CIRCLE,
                average = objNum or 3,
                allStuff = {},
        }

    return setmetatable (newObject, sp_mt)
end

function sp:status ()
    print ("objType "… self.objType)
    print ("average “… self.average)
    print (”#allStuff " … #self.allStuff)
end

------- Methods  -----

function sp:spawnStuff (sDelay)

    if self.stopped == true then return; end

    local pscale = math.random (3,6) / 10
    local pradius = 20
    
    local function onCollision( self, event )            
        – occurs, when objects collide with the obstacle    
        if event.phase == “began” then                    
            if event.other.myName == “sat1” then
               
                self.kill = true
                if (self.myName == ot.SPIKE1) or (self.myName == ot.SPIKE2) or (self.myName == ot.SPIKE3)  then
                    self.kill = false
                end
            elseif (event.other.myName == “player1”) then
                if (self.myName==ot.SPIKE1) or (self.myName==ot.SPIKE2) or (self.myName==ot.SPIKE3) or (self.myName == ot.HEART) then

                    self.kill = true
                end
            end    
        end
    end

    local function onObjShowComplete( target )   
–        if sp.stopped == true then return; end

        if target == nil then return; end
        if self.unloading then return; end
        if target.kill then return; end

        physics.addBody (target, {  density = 1.0, friction = 0.3, bounce = 0.2, radius = pradius * pscale} )

        target.collision = onCollision
        target:addEventListener (“collision”, target)

        --taret.myName = “obj” … math.random (100)
        local force = math.random( 1, 5 )        
        if math.random (2) == 1 then force = force * -.5; end
        target:applyLinearImpulse( force / 5, force, target.x, target.y )
        local torque = math.random (5, 150)
        if math.random (2) == 1 then torque = torque * -1; end
        target:applyTorque (torque)
    end

    – define needed variables    
    local x
    local y
    local size = 50
    local delay    = 50
    
    – get random values for the objects parameters

    x = math.random( sg.left + 100, sg.right - 100)
    y = math.random( 0, UP_SPAWN_LIMIT )     

–    dprint (pg.imgPath)
    if self.objType == ot.STAR1 then
        pradius = 32
        self.allStuff[#self.allStuff + 1] =
            display.newImageRect ( pg.imgPath … “mbo1” … “.png”, 64 *pscale, 64 *pscale)    
    
    elseif self.objType == ot.STAR2 then
        pradius = 32
        self.allStuff[#self.allStuff + 1] =
            display.newImageRect ( pg.imgPath … “mbo2” … “.png”, 64 *pscale, 64 *pscale)    
    elseif self.objType == ot.STAR3 then
        pradius = 32
        self.allStuff[#self.allStuff + 1] =
            display.newImageRect ( pg.imgPath … “mbo3” … “.png”, 64 *pscale, 64 *pscale)
    end

    print ("Menu–>objType "… self.objType)
    --print ("average “… self.average)
    --print (”#allStuff " … #self.allStuff)
    --print ("test # "… #self.allStuff)
    local stuff = self.allStuff [#self.allStuff]
    stuff.x = x
    stuff.y = y

    sp.view:insert (stuff)
    
    --stuff.linearDamping = 0.4
    --stuff.angularDamping = 0.6
    stuff.alpha = 0     
    stuff.xScale = 0.1
    stuff.yScale = 0.1
    stuff.rotation = math.random (360)
    stuff.myName = self.objType;  – … math.random (100)
    stuff.kill = false

    if fg.doDebug then
        stuff.tHandle = transition.to( stuff, { transition = easing.outExpo, time = 500, xScale = 1, yScale = 1, alpha = 1, delay = sDelay, onComplete = onObjShowComplete } )    
    else
        stuff.tHandle = transition.to( stuff, { transition = easingx.easeOutElastic, time = 500, xScale = 1, yScale = 1, alpha = 1, delay = sDelay, onComplete = onObjShowComplete } )    
    end    

end

function sp:removeAndAddStuff()
    
    if self.stopped == true then return; end

    local allStuff = self.allStuff

    for i=#allStuff, 1, -1 do
        --print (“y=”…oneStuff.y)
        if allStuff[i] then
            if (allStuff[i].y > sg.bottom + WIDTH ) or
                (allStuff[i].y < sg.top - WIDTH ) or
                (allStuff[i].x < sg.left - WIDTH ) or
                (allStuff[i].x > sg.right + WIDTH ) then
                    --oneStuff:removeSelf()
                    transition.cancel (allStuff[i].tHandle)

                    local oneStuff = allStuff[i]
                    display.remove (oneStuff)  – safest way?
                    --display.remove (allStuff[i])  – another way to remove object.
                    --allStuff[i]:removeSelf()  – ?!
                    oneStuff = nil
                    table.remove (allStuff,i)
            end
        end
    end

–    oneStuff = nil

    local j =  self.average - #self.allStuff
–    print (“j:”…j…"   #allStuff:"…#self.allStuff)

    if j > 0 then
        for k = 1, j do
            sp.spawnStuff (self, 0)
        end
    end

end

function sp:stop () – stop spawning
    self.stopped = true
end

function sp:start()
    self.stopped = false
end

function sp:unload () – stop everything and remove everything.
    self.stopped = true
    self.unloading = true
    print “sp: unload started…”

    for i = 1, #self.allStuff do
        if self.allStuff[i] then
            transition.cancel (self.allStuff[i].tHandle)
                    local oneStuff = table.remove (self.allStuff,i)

                    if oneStuff ~=nil then    
                        oneStuff:removeSelf()
                        oneStuff=nil
                    end
        end
    end
end

function sp:startCreatingStuff( )    
    – create the objects
    self.stopped = false
    for i = 1, self.average do
        sp.spawnStuff (self, 0 )–i*math.random (200,500))
    end    
end

return sp
 

It could be.  Do you ever call removeAndAddStuff?  If not, then that would be a source of a Lua memory leak.

  • Andrew

I call it on enterFrame.  … 

There’s nothing wrong with removeAndAddStuff() itself, then?

Can you give me an example of what kind of code can cause Lua memory leak?

Thanks for your help Andrew

I think I found the troubled area (menu system), if this is a memory leak.  It drops back momentarily, so I’m not sure.  Do I need to keep this running longer?

           from MAIN.lua
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    0.474    Mb
 ---->Texture Memory Used:    2.097    Mb
 ------------------------------------------
 

           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    0.584    Mb
 ---->Texture Memory Used:    2.097    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    0.670    Mb
 ---->Texture Memory Used:    6.357    Mb
 ------------------------------------------

           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.225    Mb
 ---->Texture Memory Used:    11.141    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    0.952    Mb
 ---->Texture Memory Used:    11.207    Mb
 ------------------------------------------

           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.527    Mb
 ---->Texture Memory Used:    11.796    Mb
 
           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.175    Mb
 ---->Texture Memory Used:    11.862    Mb
 ------------------------------------------

           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.930    Mb
 ---->Texture Memory Used:    12.452    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.462    Mb
 ---->Texture Memory Used:    12.517    Mb
 ------------------------------------------

           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    2.068    Mb
 ---->Texture Memory Used:    13.107    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.304    Mb
 ---->Texture Memory Used:    13.173    Mb
           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.845    Mb
 ---->Texture Memory Used:    13.763    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    2.357    Mb
 ---->Texture Memory Used:    13.828    Mb
           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.386    Mb                        <---- dropped back here
 ---->Texture Memory Used:    14.418    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.894    Mb
 ---->Texture Memory Used:    14.483    Mb
           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    2.459    Mb
 ---->Texture Memory Used:    15.073    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    2.971    Mb
 ---->Texture Memory Used:    15.139    Mb
           scene1
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    1.965    Mb
 ---->Texture Memory Used:    15.729    Mb
 

           scene2
 ---------MEMORY INFORMATION*---------
 —>System Memory Used:    2.519    Mb
 ---->Texture Memory Used:    15.794    Mb

As long as it’s not continuously increasing, you should be fine.

By the way, make sure you call collectgarbage() immediately before printing out the memory usage.  This forces the Lua garbage collector to reclaim memory.  If you’re not doing that, then the Lua memory output you see will still be indicatively correct, but it’ll be harder to discern a pattern.

If you call collectgarbage() before printing the memory usage, then the pattern (if any) becomes much clearer.  What you don’t want to see is a clearly increasing pattern.  Jumps up and down are fine as long as it’s not overall trending upwards.

  • Andrew

I found that I forgot to call one of the clean up functions.  I also updated all “module package see all…” to “M = {}…” 

Anyways, thank you again Andrew.  I don’t think I could’ve done this if I felt that I was all alone and scared he he.