Memory Problems and can't find it...

Hello there and… I need a huge help !

I’ve been testing and testing my app for almost a week now, and tried to clean it, but still : printing MemUsage and TextMem indicates that I’ve got a leak.

I’m running the app according to this way, 4 times, doing the exact same things :

  • “StartScreen”
  • “LoadingScreen”
  • “Gameplay Screen”
  • “StartScreen”. (x4 Times)

After 4 iterations, MemUsage goes slowly but surely from 1171.105 kb to 1186.101 kb while the TextMem goes from  38.942mb to  38.954mb , which is surprisely not that much (but still, doesn’t stop going up   :( )

I can’t post any code here, only because it’s a lot of code, separated in many files. But I can explain its structure. I’ve got zero global variables (I’ve used this script to be sure : http://spiralcodestudio.com/corona-sdk-pro-tip-of-the-day-10/)

  • I’m using the Storyboard API (because I’ve started the game before Composer arrived)
  • Main functions, which are used in the createScene / enterScene / exitScene are forward declared. All the other variables and functions, which are essensially used in “createScene” are localised.
  • For each scene, gameGroup is declared as the self.view
  • Every display object is then added to a display group called “gameDisplayGroup”
  • “gameDisplayGroup” is finally inserted in “gameGroup” (the self.view).
  • When exiting each scene, I’m niling all the variables which are forward declared.
  • All required files are “local” to each scene, and unloaded at the end, and then niled too.
  • I’m using the “myData” technique to avoid having global variables, and they’re unloaded and niled too.

But still can’t found where’s leaks are coming from. I’ve even tried to apply a transparency to gameDisplayGroup to be sure that all the new display objects would be created in that group, and therefore get the same transparency… So that would mean that all the display object are in the self.view, and when quitting the scene, it would be automatically removed ?

And I also tried to removeAllScene after every change of scene, but it doesn’t seems to help me (it reduces the memory, but doesn’t clean everything…).

So, I guess it will be hard for you to help me without seeing the code, but after having read a lot of blog posts and tutorials, I can’t figure out what I am missing.

So if you have any idea about what I should check… Thank you in advance, you’d be saving my life right now since I’m kinda depressed  :slight_smile:

EDIT : And forgot to say that I’m not using any external modular that would have memory leaks I can’t control…

** FIXED CODE FORMATTING ERRORS **

@evanspro,

  1. Be sure you’re testing memory correctly first.  It is easy to forget to do a collect step before measuring.  I suggest making a module with this code or something like it and then require that module in any file that needs to test memory and use the same call everywhere:

    – Easy leak testing module: memleak.lua – Helper functions – local round = function (val, n) if (n) then return math.floor( (val * 10^n) + 0.5) / (10^n) else return math.floor(val+0.5) end end local public = {} local lastMem public.check = function( doMsg, collect ) if( collect ~= false) then collectgarbage( “collect” ) end local curMem = collectgarbage(“count”) if(lastMem == nil) then lastMem = curMem end local delta = curMem - lastMem lastMem = curMem if(doMsg ~= false) then print( "Current mem: ", round(curMem,2), " Last mem: ", round(lastMem,2), " Change in mem: ", round(delta,2) ) end return curMem,lastMem,delta end return public

later in your different files:

local memleak = require "memleak.lua" ... memleak.check() -- Collect memory and print message by default.
  1. Double check that in your scenes, you are adding all display objects to group and, if your groups are separate from the one provided by the scene be sure the groups themselves are added to the scene group.  Otherwise, your display objects will not be cleaned up automatically by storyboard/composer.

  2. More ideas if they occur later…

Cheers,

Ed

Note: The above was derived from my article code here: http://roaminggamer.com/2014/06/26/simple-bench-for-corona-sdk/ 

@roaminggamer : thanks you for your answer. Although I haven’t been able to try your easy leak testing.

When I try to call “memleak.test()”, I’ve got an error ("attempt to call field ‘test’ (a nil value)).

Sorry about that, I should have made that 

memleak.check()

I have corrected the example.

(Note: Most of the time I write short snippets like the above and only run them in my head.  Looks like I have a memory leak of my own. :slight_smile:)

Tried to change memleak.test() with memleak.check(), didn’t seem to work at first, don’t know why.

But well, now it works :slight_smile: So thanks again and it’s a lot easier for me to track the difference between scenes.

Unfortunately, I’ve got the same results as before… I’m gonna have to keep digging !

You say that you have

  • “StartScreen”
  • “LoadingScreen”
  • “Gameplay Screen”
  • “StartScreen”. (x4 Times)

What about deleting all of the objects you created?

Even if you call the storyboard api to removeAllScene that only works if you added the object to the actual scene with the scene:insert(object).

This goes for timers and images everything.

So if you want the storyboard api to clean it up automatically you need to add it to the scene or in the stoyboard exit ( or whatever it is ) you need to loop through all object and remove / cancel them yourself.

Good Luck,

Larry Meadows

Well, I said that :

  • For each scene, gameGroup is declared as the self.view
  • Every display object is then added to a display group called “gameDisplayGroup”
  • “gameDisplayGroup” is finally inserted in “gameGroup” (the self.view).

So, this is basically what you are saying ? Except that I’m adding every display object in the same display group right before. Or am I wrong ?

Your are dead on.

the only other think I can think of is to make a note of your memory on a couple round trips going from scene to scene

then in the exit - manually remove several of your bigger memory items, and stop a couple timers.

Just do it for like 10 or 15 items manually and see if you can notice a difference when you try it again.

This will tell you if storyboard is looping through cleaning things up or not.

also as a last resort switching from stroryboard to composer api is pretty painless.

Larry

do you

Thanks for the suggestions ! So for now : after triple checking that everything was actually included in groups (and main scene group), I’m trying to remove like you just said every big stuff. And then, I’ll give composer API a shot… And if it still doesn’t work, guess i’ll be back here for more ideas :wink:

… But isn’t that weird that the LUA Memory increases a lot faster than the texture memory (if i’m not wrong in my conversions, which is possible)  ? I thought the contrary was the most common.

So… Crossing my fingers (and trying to not having a nervous breakdown   ! 

Edit : Forget about my conversions :wink:

If you are loading the exact same images texture memory should increase faster as corona tries to clone images in memory for a smaller foot print. Think of a texture just setting in memory and two pointers are point at the same address. since the texture is only loaded once adding another pointer should be pretty lightweight. ( if that is how they are doing it ).

Good luck

Larry

Don’t forget to also nil out references to tables and variables (i assume you are doing that already).

Hope this helps

Gremin Interactive : are you saying we need to nil out references to tables, even if they are local ? And if so, everything that’s inside of the table and the tables themselves ?

Because I’m not sure to understand… Do I have to do this ?

-- At start local table = {} table[1] = "John" table[2] = "Michael" -- When leaving the scene for i=1, #table do table[i] = nil end table = nil

And I was also wondering. Do we need to nil out timers and transitions, even if they are finished ? I’m already using some kind of “timer” and “transitions” managers : everytime I use transition.to() or timer.performWithDelay(), i add a reference to a table so that I can remove them when exiting the scene…

… Ok, I think I’ve found where the memory leaks are coming from (or at least, one of the source). I’ve been able to reproduce it in a blank project, based on the same structure.

So, it’s basically composed of 5 files (code below, or**you can download the zip file here**): 

  • Main.lua :
  • Screen_1.lua
  • Screen_2.lua
  • Mydata.lua
  • Properties_colors.lua

At first, we load "Main, which, using the Storyboard API, immediatly loads “Screen_1”. Then, a button is created : once tapped, it goes to “Screen_2” where an other button is created. If tapped, we go back to “Screen_2”. At every tap, I remove the current scene.

The trick is that, in each scene, I load those external modules : MyData and PropertiesColors. The first one allows me to avoid global variables and the second one is random data.

So, when I jump from scene 1 to scene 2, quickly, you can see in the console that the LUA memory is growing.

I’m pretty convinced it is because of the way I load or unload myData and PropertiesColors… And I have no idea why.

So if you wanna check it out, the code for the files is here :

  • Main.lua :

    local storyboard = require “storyboard” local scene = storyboard.newScene() storyboard.purgeOnSceneChange = true ------------------------------------------------------------------- – HIDE STATUS BAR ------------------------------------------------------------------- display.setStatusBar(display.HiddenStatusBar) ------------------------------------------------------------------- – MONITORING ------------------------------------------------------------------- local monitorMem = function() collectgarbage() print("MemUsage - Maingame : " … collectgarbage(“count”)) local textMem = system.getInfo(“textureMemoryUsed”) / 1000000 print( "TexMem: " … textMem ) end Runtime:addEventListener(“enterFrame”, monitorMem) ------------------------------------------------------------------- – SHOWING THE STARTSCREEN ------------------------------------------------------------------- storyboard.gotoScene(“screen_1”)

  • Screen_1.lua

    ------------------------------------------------------------------- – LOADING EXTERNAL MODULES ------------------------------------------------------------------- storyboard = require(“storyboard”) scene = storyboard.newScene() _myData = require(“mydata”) colorsLoaded = require(“properties_colors”) ------------------------------------------------------------------- – STORYBOARD - CREATESCENE ------------------------------------------------------------------- function scene:createScene(event) local gameGroup = self.view – Function that will be called later local function gotoScene(event) if event.phase == “ended” then storyboard.gotoScene(“screen_2”) end end – We create a button local bouton = display.newRect(0,0,200,200) bouton:setFillColor(1,0,0) bouton.x = 300; bouton.y = 300; – On which we apply a touch event listener bouton:addEventListener(“touch”, gotoScene) – Button is inserted into gameGroup gameGroup:insert(bouton) end ------------------------------------------------------------------- – STORYBOARD - ENTERSCENE ------------------------------------------------------------------- function scene:enterScene(event) local gameGroup = self.view storyboard.removeScene(“screen_2”) end ------------------------------------------------------------------- – STORYBOARD - ENTERSCENE ------------------------------------------------------------------- function scene:exitScene(event) local gameGroup = self.view package.loaded[“engine_colors”] = nil storyboard.removeScene(“screen_1”) end scene:addEventListener(“createScene”, scene) scene:addEventListener(“enterScene”, scene) scene:addEventListener(“exitScene”, scene) return scene

  • Screen_2.lua

    ------------------------------------------------------------------- – LOADING EXTERNAL MODULES ------------------------------------------------------------------- storyboard = require(“storyboard”) scene = storyboard.newScene() _myData = require(“mydata”) colorsLoaded = require(“properties_colors”) ------------------------------------------------------------------- – STORYBOARD - CREATESCENE ------------------------------------------------------------------- function scene:createScene(event) local gameGroup = self.view – Function that will be called later local function gotoScene(event) if event.phase == “ended” then storyboard.gotoScene(“screen_1”) end end – We create a button local bouton = display.newRect(0,0,200,200) bouton:setFillColor(0,1,0) bouton.x = 300; bouton.y = 300; – On which we apply a touch event listener bouton:addEventListener(“touch”, gotoScene) – Button is inserted into gameGroup gameGroup:insert(bouton) end ------------------------------------------------------------------- – STORYBOARD - ENTERSCENE ------------------------------------------------------------------- function scene:enterScene(event) local gameGroup = self.view storyboard.removeScene(“screen_1”) end ------------------------------------------------------------------- – STORYBOARD - ENTERSCENE ------------------------------------------------------------------- function scene:exitScene(event) local gameGroup = self.view package.loaded[“engine_colors”] = nil storyboard.removeScene(“screen_2”) end scene:addEventListener(“createScene”, scene) scene:addEventListener(“enterScene”, scene) scene:addEventListener(“exitScene”, scene) return scene

  • Mydata.lua

    local M = {} return M

  • - Properties_colors.lua

    --------------------------------------------------------------------------------------------------------------------------------- – --------------------------------------------------------------------------------------------------------------------------------- _myData = require (“mydata”) _myData.itemColors = {} _myData.itemColors[1] = { {r=79, v=79, b=79}, {r=211, v=79, b=179}, {r=9, v=201, b=229}, {r=237, v=41, b=133}, {r=255, v=170, b=22} } _myData.itemColors[2] = { {r=79, v=79, b=79}, {r=211, v=79, b=179}, {r=9, v=201, b=229}, {r=237, v=41, b=133}, {r=255, v=170, b=22} } _myData.itemColors[3] = { {r=255, v=255, b=255}, {r=221, v=114, b=195}, {r=65, v=219, b=241}, {r=251, v=86, b=163}, {r=255, v=183, b=57} } _myData.itemColors[4] = { {r=79, v=79, b=79}, {r=211, v=79, b=179}, {r=9, v=201, b=229}, {r=237, v=41, b=133}, {r=255, v=170, b=22} } _myData.itemColorsDefault = {} _myData.itemColorsDefault[1] = {237, 41, 133}; _myData.itemColorsDefault[2] = {55, 199, 156}; _myData.itemColorsDefault[3] = {255, 255, 255}; _myData.itemColorsDefault[4] = {237, 41, 133}; _myData.worldColors = {} _myData.worldColors[1] = {} _myData.worldColors[1] = { backgroundColor1 = {r=245, v=247, b=176}, backgroundColor2 = {r=110, v=215, b=183}, backgroundColor3 = {r=154, v=239, b=214}, cloudColor = {r=162, v=240, b=216}, groundColor1 = {r=217, v=89, b=51}, groundColor2 = {r=234, v=114, b=114}, groundColor3 = {r=199, v=71, b=33}, plantColor1 = {r=64, v=146, b=128}, plantColor2 = {r=255, v=255, b=255}, pustuleColor = {r=0, v=0, b=0}, ropeColor = {r=0, v=0, b=0}, bonusColor = {r=0, v=0, b=0}, particles = {r=73, v=191, b=155} } _myData.worldColors[2] = {} _myData.worldColors[2] = { backgroundColor1 = {r=211, v=165, b=221}, backgroundColor2 = {r=229, v=190, b=238}, backgroundColor3 = {r=241, v=212, b=248}, cloudColor = {r=242, v=219, b=247}, groundColor1 = {r=208, v=42, b=124}, groundColor2 = {r=246, v=78, b=161}, groundColor3 = {r=200, v=35, b=117}, plantColor1 = {r=148, v=79, b=163}, plantColor2 = {r=255, v=255, b=255}, pustuleColor = {r=0, v=0, b=0}, ropeColor = {r=0, v=0, b=0}, bonusColor = {r=0, v=0, b=0}, particles = {r=182, v=109, b=198} } _myData.worldColors[3] = {} _myData.worldColors[3] = { backgroundColor1 = {r=211, v=165, b=221}, backgroundColor2 = {r=229, v=190, b=238}, backgroundColor3 = {r=241, v=212, b=248}, cloudColor = {r=0, v=0, b=0}, groundColor1 = {r=208, v=42, b=124}, groundColor2 = {r=246, v=78, b=161}, groundColor3 = {r=200, v=35, b=117}, plantColor1 = {r=90, v=175, b=73}, plantColor2 = {r=255, v=255, b=255}, pustuleColor = {r=0, v=0, b=0}, ropeColor = {r=0, v=0, b=0}, bonusColor = {r=0, v=0, b=0} } _myData.worldColors[4] = {} _myData.worldColors[4] = { backgroundColor1 = {r=211, v=165, b=221}, backgroundColor2 = {r=229, v=190, b=238}, backgroundColor3 = {r=241, v=212, b=248}, cloudColor = {r=0, v=0, b=0}, groundColor1 = {r=208, v=42, b=124}, groundColor2 = {r=246, v=78, b=161}, groundColor3 = {r=200, v=35, b=117}, plantColor1 = {r=90, v=175, b=73}, plantColor2 = {r=255, v=255, b=255}, pustuleColor = {r=0, v=0, b=0}, ropeColor = {r=0, v=0, b=0}, bonusColor = {r=0, v=0, b=0} } _myData.worldColors[5] = {} _myData.worldColors[5] = { backgroundColor1 = {r=211, v=165, b=221}, backgroundColor2 = {r=229, v=190, b=238}, backgroundColor3 = {r=241, v=212, b=248}, cloudColor = {r=0, v=0, b=0}, groundColor1 = {r=208, v=42, b=124}, groundColor2 = {r=246, v=78, b=161}, groundColor3 = {r=200, v=35, b=117}, plantColor1 = {r=90, v=175, b=73}, plantColor2 = {r=255, v=255, b=255}, pustuleColor = {r=0, v=0, b=0}, ropeColor = {r=0, v=0, b=0}, bonusColor = {r=0, v=0, b=0} } _myData.worldColors[6] = {} _myData.worldColors[6] = { backgroundColor1 = {r=211, v=165, b=221}, backgroundColor2 = {r=229, v=190, b=238}, backgroundColor3 = {r=241, v=212, b=248}, cloudColor = {r=0, v=0, b=0}, groundColor1 = {r=208, v=42, b=124}, groundColor2 = {r=246, v=78, b=161}, groundColor3 = {r=200, v=35, b=117}, plantColor1 = {r=90, v=175, b=73}, plantColor2 = {r=255, v=255, b=255}, pustuleColor = {r=0, v=0, b=0}, ropeColor = {r=0, v=0, b=0}, bonusColor = {r=0, v=0, b=0} } _myData.worldColors[7] = {} _myData.worldColors[7] = { backgroundColor1 = {r=211, v=165, b=221}, backgroundColor2 = {r=229, v=190, b=238}, backgroundColor3 = {r=241, v=212, b=248}, cloudColor = {r=0, v=0, b=0}, groundColor1 = {r=208, v=42, b=124}, groundColor2 = {r=246, v=78, b=161}, groundColor3 = {r=200, v=35, b=117}, plantColor1 = {r=90, v=175, b=73}, plantColor2 = {r=255, v=255, b=255}, pustuleColor = {r=0, v=0, b=0}, ropeColor = {r=0, v=0, b=0}, bonusColor = {r=0, v=0, b=0} } _myData.worldColors[8] = {} _myData.worldColors[8] = { backgroundColor1 = {r=211, v=165, b=221}, backgroundColor2 = {r=229, v=190, b=238}, backgroundColor3 = {r=241, v=212, b=248}, cloudColor = {r=0, v=0, b=0}, groundColor1 = {r=208, v=42, b=124}, groundColor2 = {r=246, v=78, b=161}, groundColor3 = {r=200, v=35, b=117}, plantColor1 = {r=90, v=175, b=73}, plantColor2 = {r=255, v=255, b=255}, pustuleColor = {r=0, v=0, b=0}, ropeColor = {r=0, v=0, b=0}, bonusColor = {r=0, v=0, b=0} } 

I’m pretty sure this is a basic problem (I’m pretty confused about how Corona deals with memory and modules), but I can’t think clearly enough now :slight_smile: And if you guys help me with that, you’d be saving my whole project :slight_smile:

Out of interest, why are: storyboard, scene, _myData and colorsLoaded all global variables ?

The underscore character “_” has no special meaning.

At first, they were all local, in each files. And I declared them as global to see if it would change anything.

And about the underscore character : it was just for me. In the original project, they are several others files like mydata and I needed to see all their references quick.

… And, by the way : i’ve done some other tests : removing every reference to myData and colorsLoaded. There are never loaded but still… when you go from Scene_1 to Scene_2, etc, memory does still increase. So am I missing something else or is this just storyboard / corona related ? 

I would put them back to local.

Also don’t nil those out, require only requires a file once, if it is already loaded. so you could have 100 requires of the same file across your code, but it still only get’s loaded once. So that isn’t the source.

I put them back to local and removed the line where propertieColors where nilled.

And, I manually removed the button in each scene, right before changing scenes (I had to forward declare it before). So the function changeScene now looks like this :

-- Function that will be called later local function gotoScene(event) if event.phase == "ended" then bouton:removeSelf(); bouton = nil; storyboard.gotoScene("screen\_1") end end

And now that the button is removed, there is no more memory leak ! Which brings one question : once a display object is added to the scene.view, isn’t it supposed to be properly removed and niled when changing scene thanks to the Storyboard API ?

Storyboard removes the display objects, im not sure if it Nils the references or not though.

I generally nil them to be sure in exitscene or whatever.

Glad to hear that it’s fixed :slight_smile:

Mmmm well, if it doesn’t nil the references… i’m gonna have a very bad time now :slight_smile: But where should I nil all of them ? In exitScene ? Or right before I call gotoScene() ? Or at the end of each scene:createScene / scene:enterScene / scene:exitScene ?

Hope that it will fix my problem (or at least, a huge part of it) !

Well thanks to all of you anyway !  :slight_smile: