I don’t use storyboard myself so I’d wait for someone more experienced with storyboard to weigh in before you take my assumption as gospel
Cheers
I don’t use storyboard myself so I’d wait for someone more experienced with storyboard to weigh in before you take my assumption as gospel
Cheers
Try taking out the removeScene from your exitScene function (in every storyboard scene). You’re already removing the scenes during your enterScene event anyway.
I’ve already removed the removeScene from the exitScene function. Instead, I’ve got removeAll() in enterScene of each scene to be sure.
Right now, the texture memory increase of 0.004 mb every time (which seems not too bad ?) but still, LUA memory is still increasing every time of 10~20 kb. Actually, I have on idea how bad it is…
** FIXED CODE FORMATTING ERRORS **
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.
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.
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. )
Tried to change memleak.test() with memleak.check(), didn’t seem to work at first, don’t know why.
But well, now it works 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
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 :
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
… 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
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**):
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 And if you guys help me with that, you’d be saving my whole project
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 ?