Need clarity about memory handling

I’m finishing up a Corona project, and I want to make sure I’m getting the memory handling right. So here’s my questions:

  1. When I run storyboard.purgeScene(), are all the scene’s display objects and their event listeners automatically deallocated, or is there anything I need to explicitly deallocate even if they belong to the scene’s view object?

[lua]local image

function scene:createScene(event)
image = display.newImage(“image.png”)
image:addEventListener(“touch”, onTouch)
self.view:insert(image) – insert image into scene
end

function scene:exitScene(event)
storyboard.purgeScene()
– image:removeEventListener(“touch”, onTouch) – NECESSARY?
– display:remove(image) – NECESSARY?
– image = nil – NECESSARY?
end[/lua]

  1. Is display.remove(object) superior to object:removeSelf()?

  2. Is it necessary to set object = nil after both display.remove(object) and object:removeSelf()?

  3. The “Memory Leaks” article says: “When changing text, change the text object directly, don’t keep creating new text objects.” But Rob Miracle said it was totally safe to redraw the whole scene whenever. So which is right?

  4. When playing short-lived, non-looping sound effects repeatedly with audio.play(), will Corona automatically deallocate memory when the sound effect is done? Or how and when should I deallocate it?

[import]uid: 73434 topic_id: 34463 reply_id: 334463[/import]

1. When I run storyboard.purgeScene(), are all the scene’s display objects and their event listeners automatically deallocated, or is there anything I need to explicitly deallocate even if they belong to the scene’s view object?

Anything created and added to the scene’s view will be removed and the memory released. Event listeners attached to those objects are also removed and released. What you have to remove is the following:

  1. any timers that are still running
  2. any transition.to’s that call an “onComplete” call back
  3. and audio that’s playing that calls an onComplete call back.
    3a dispose of any audio loaded by the scene.
  4. Any Runtime listeners you have created
  5. Any thing that’s calling back to a function defined in your scene that references any of your display objects.

2. Is display.remove(object) superior to object:removeSelf()?
display.remove(object) is practically identical to:

 if object and object.removeSelf then  
 object:removeSelf()  
 end  

You get the test to make sure the object exists for free. You are still responsible for setting the variable to nil so garbage collection will remove it.

3. Is it necessary to set object = nil after both display.remove(object) and object:removeSelf()?
Yes.

4. The “Memory Leaks” article says: “When changing text, change the text object directly, don’t keep creating new text objects.” But Rob Miracle said it was totally safe to redraw the whole scene whenever. So which is right?
These are kind of different. If you are removing the text object by purging the scene, you have to recreate it. If you’re not removing the text object, you want to use it’s .text property to change it. Let me try to illustrate:

local mytext = display.newText("Hello World")  
local mytext = display.newText("Happy New Year")  

The 2nd invocation overwote mytext which is a pointer to the object created in the first one. You have now lost access to the first display object that you created and that’s a memory leak. You can’t get back to it to remove it.

local mytext = display.newText("Hello World")  
mytext:removeSelf()  
mytext = nil  
local mytext = display.newText("Happy New Year")  

shouldn’t leak memory since you took the effort to destroy the first version. If you are removing everything, it’s safe to recreate everything. If you are not recreating everything, you need to update the objects using their update methods.

5. When playing short-lived, non-looping sound effects repeatedly with audio.play(), will Corona automatically deallocate memory when the sound effect is done? Or how and when should I deallocate it?
You are responsible for disposing of your audio. But if you’re going to be using the same clip over and over, like say a beep when someone presses a button, or an explosion sound, load it in main.lua or somewhere, and just reuse that sound over and over. In other words:

Load
play
play
play to your hearts content
dispose

If its a big sound that doesn’t get used often, like say a voice over that only exists for that scene, then you probably should dispose them. If your using storyboard and your loading the audio in your createScene() you should dispose those as well in the destoryScene so they don’t keep getting loaded over and over. If you load it in enterScene() dispose them in exitScene().
[import]uid: 199310 topic_id: 34463 reply_id: 137025[/import]

Rob - anyway we can get your response added as a sticky to the Storyboard forum: https://developer.coronalabs.com/forums/storyboard ?

I think it would be another good resource for those starting out with Storyboard. [import]uid: 190375 topic_id: 34463 reply_id: 137111[/import]

@Rob: Very good answer! Everything is much clearer now.

I have a few follow-up questions:

5b. If I have just a few sound effects in my app that are used all around, can’t they simply live in the global scope of the app? (If I, on the other hand, had a large set of audio files in scene A, and a different large set of audio files in scene B, I would definitely see the need for deallocating audio after each scene).

  1. In the following code, do I need to nil both variables, or just one of them to cause deallocation?

[lua]local a = display.newText(“Hello World”)
local b = a
display:remove(a)
a = nil
b = nil – NECESSARY?[/lua]
[import]uid: 73434 topic_id: 34463 reply_id: 137115[/import]

1. When I run storyboard.purgeScene(), are all the scene’s display objects and their event listeners automatically deallocated, or is there anything I need to explicitly deallocate even if they belong to the scene’s view object?

Anything created and added to the scene’s view will be removed and the memory released. Event listeners attached to those objects are also removed and released. What you have to remove is the following:

  1. any timers that are still running
  2. any transition.to’s that call an “onComplete” call back
  3. and audio that’s playing that calls an onComplete call back.
    3a dispose of any audio loaded by the scene.
  4. Any Runtime listeners you have created
  5. Any thing that’s calling back to a function defined in your scene that references any of your display objects.

2. Is display.remove(object) superior to object:removeSelf()?
display.remove(object) is practically identical to:

 if object and object.removeSelf then  
 object:removeSelf()  
 end  

You get the test to make sure the object exists for free. You are still responsible for setting the variable to nil so garbage collection will remove it.

3. Is it necessary to set object = nil after both display.remove(object) and object:removeSelf()?
Yes.

4. The “Memory Leaks” article says: “When changing text, change the text object directly, don’t keep creating new text objects.” But Rob Miracle said it was totally safe to redraw the whole scene whenever. So which is right?
These are kind of different. If you are removing the text object by purging the scene, you have to recreate it. If you’re not removing the text object, you want to use it’s .text property to change it. Let me try to illustrate:

local mytext = display.newText("Hello World")  
local mytext = display.newText("Happy New Year")  

The 2nd invocation overwote mytext which is a pointer to the object created in the first one. You have now lost access to the first display object that you created and that’s a memory leak. You can’t get back to it to remove it.

local mytext = display.newText("Hello World")  
mytext:removeSelf()  
mytext = nil  
local mytext = display.newText("Happy New Year")  

shouldn’t leak memory since you took the effort to destroy the first version. If you are removing everything, it’s safe to recreate everything. If you are not recreating everything, you need to update the objects using their update methods.

5. When playing short-lived, non-looping sound effects repeatedly with audio.play(), will Corona automatically deallocate memory when the sound effect is done? Or how and when should I deallocate it?
You are responsible for disposing of your audio. But if you’re going to be using the same clip over and over, like say a beep when someone presses a button, or an explosion sound, load it in main.lua or somewhere, and just reuse that sound over and over. In other words:

Load
play
play
play to your hearts content
dispose

If its a big sound that doesn’t get used often, like say a voice over that only exists for that scene, then you probably should dispose them. If your using storyboard and your loading the audio in your createScene() you should dispose those as well in the destoryScene so they don’t keep getting loaded over and over. If you load it in enterScene() dispose them in exitScene().
[import]uid: 199310 topic_id: 34463 reply_id: 137025[/import]

Rob - anyway we can get your response added as a sticky to the Storyboard forum: https://developer.coronalabs.com/forums/storyboard ?

I think it would be another good resource for those starting out with Storyboard. [import]uid: 190375 topic_id: 34463 reply_id: 137111[/import]

@Rob: Very good answer! Everything is much clearer now.

I have a few follow-up questions:

5b. If I have just a few sound effects in my app that are used all around, can’t they simply live in the global scope of the app? (If I, on the other hand, had a large set of audio files in scene A, and a different large set of audio files in scene B, I would definitely see the need for deallocating audio after each scene).

  1. In the following code, do I need to nil both variables, or just one of them to cause deallocation?

[lua]local a = display.newText(“Hello World”)
local b = a
display:remove(a)
a = nil
b = nil – NECESSARY?[/lua]
[import]uid: 73434 topic_id: 34463 reply_id: 137115[/import]

@Rob: Some more things about the memory handling. I’m finishing up my game, and really want to get all of this right.

  • Reply #1, Question 1: When removing timers, transitions, audio, and runtime listeners, like you said, do you also have to set all these to nil?

  • Reply #1, Question 1: How are transitions and audio with “onComplete” are different from those who don’t have “onComplete”?

  • Reply #1, Question 1: You wrote “Anything that’s calling back to a function defined in your scene that references any of your display objects.” Could you give a concrete code example showing what you mean here?

  • Reply #3, Question 5b and 6: Still waiting for an answer here.

  • Question 7 (new): Widgets created with widget.newSomething() and added to a group, will they be completely deallocated when their parent group is deleted, or do they need some extra attention, widgets being a special type of object? [import]uid: 73434 topic_id: 34463 reply_id: 139034[/import]

5b. If I have just a few sound effects in my app that are used all around, can’t they simply live in the global scope of the app? (If I, on the other hand, had a large set of audio files in scene A, and a different large set of audio files in scene B, I would definitely see the need for deallocating audio after each scene).

Correct. I would go so far to say that even if you have a long clip that you use frequently, load it once in some form of global scope and keep it. You would probably pay a bigger performance penalty disposing and reloading it. But if you have sounds unique to a scene and you won’t be back there for a while (or ever) dispose them.

6. In the following code, do I need to nil both variables, or just one of them to cause deallocation?

Yes you must. I conducted a simple test using a variant of your code:

local a = display.newCircle(0,0,100)  
local b = a  
display:remove(a)  
a = nil  
print(b)  

And this was printed:

2013-01-15 18:20:35.333 Corona Simulator[465:707] table: 0x1007dda40  

So clearly B is holding onto the pointer and the garbage collector will think you are still retaining this memory and not de-allocate it.

- Reply #1, Question 1: When removing timers, transitions, audio, and runtime listeners, like you said, do you also have to set all these to nil?
Timers, yes. Transitions, if you save a reference to them, then yes you do. In other words:

myTrans = transition.to(a, {time=500, x=100, y=100, onComplete = checkit})  

myTrans will have a reference to the transition when your checkit() function runs, but the transition is over, so there is nothing to cancel so you should nil the pointer. If you cancel the transition, you are still left with a value in the pointer, and you need to nil that as well.

As for Runtime listeners, I don’t believe there is anything you can nil as you don’t ever get a pointer back from any of the add listener functions.

- Reply #1, Question 1: How are transitions and audio with “onComplete” are different from those who don’t have “onComplete”?
With regards to memory, I don’t believe there are any.

- Reply #1, Question 1: You wrote “Anything that’s calling back to a function defined in your scene that references any of your display objects.” Could you give a concrete code example showing what you mean here?
I’m not sure I would have all of them in memory. But as an example, you call network.download() to fetch a photo from Flickr and you give it a call back to then create the display object for it and shove it into your scene’s view. Before that photo downloads, your user navigates away and you purge the scene. That callback function is still active and the system will call that function and try to execute the code. If that function doesn’t exist or the group you’re putting the object into doesn’t exist you can expect a SIGSEGV segment violation or bus error crash.

- Question 7 (new): Widgets created with widget.newSomething() and added to a group, will they be completely deallocated when their parent group is deleted, or do they need some extra attention, widgets being a special type of object?
They should be completely dealocated just like a display.newImageRect() would be. [import]uid: 199310 topic_id: 34463 reply_id: 139092[/import]

@Rob: I am amazed at the precise answers you just provided. This was extremely useful in cleaning up our project. I think I have but one more question regarding timers. Let’s call this Question 8.

What I am basically trying to figure out, is it you have to assign all timers to variables in order to deallocate them, or if it’s quite the opposite, that you need to deallocate them if you have assigned them to a variable.

8a. The timer is not assigned to a variable. Will the timer take complete care of its own deallocation when it has finished running?

[lua]function listener(event)
end
timer.performWithDelay(1000, listener)[/lua]

8b. Again, the timer is not assigned to a variable. Is calling timer.cancel() from within the timer function sufficient to deallocate the timer (to stop it before its natural end)?

[lua]function listener(event)
if event.count > 5 then
timer.cancel(event.source)
–event.source = nil – is this necessary (or even meaningful?)
end
end
timer.performWithDelay(1000, listener, 10)[/lua]

8c. Now, the timer is assigned to a variable. I assume here that deallocation can be done with timer.cancel() and nil-ing both inside and outside the listener function?

[lua]local var
function listener(event)
timer.cancel(var)
var = nil
end
var = timer.performWithDelay(1000, listener)
timer.cancel(var)
var = nil[/lua]

PS! I assume how I should deallocate timers will also be applicable to transitions?
[import]uid: 73434 topic_id: 34463 reply_id: 139152[/import]

8a. The timer is not assigned to a variable. Will the timer take complete care of its own deallocation when it has finished running?

I don’t have a good way to test this, but since this method does not return a pointer for you to cancel and nil out, I would assume it would clear itself up but after trying to get a good read on it, I do see memory going up. I’m going to bring this up to the team.

8b. Again, the timer is not assigned to a variable. Is calling timer.cancel() from within the timer function sufficient to deallocate the timer (to stop it before its natural end)?

If you nil event.source, it appears that it’s sufficient to clear the memory.

function listener(event)  
 if event.count \> 5 then  
 timer.cancel(event.source)  
 --event.source = nil -- is this necessary (or even meaningful?)  
 event.source = nil  
 print(gcinfo())  
 collectgarbage("collect")  
 print(gcinfo())  
 end  
end  
  
collectgarbage("collect")  
print(gcinfo())  
timer.performWithDelay(100, listener, 10)  

results in:

2013-01-16 18:45:59.876 Corona Simulator[452:707] 117  
2013-01-16 18:46:00.667 Corona Simulator[452:707] 126  
2013-01-16 18:46:00.668 Corona Simulator[452:707] 117  

8c. Now, the timer is assigned to a variable. I assume here that deallocation can be done with timer.cancel() and nil-ing both inside and outside the listener function?
Most certainly if you return the timer into a variable and cancel it early, you should nil the pointer.
[import]uid: 199310 topic_id: 34463 reply_id: 139236[/import]

8a. Timers and transitions functions will clean themselves up after they fire or canceled. You should nil any variables that contain the handles returned from the timers/transitions after they finish in order to free up the variables themselves.

8b. Canceling a timer from within the listener will deallocate the timer.

event.source within a listener is a local variable that goes away when the listener function ends. There is no need to nil the variable. [import]uid: 7559 topic_id: 34463 reply_id: 139296[/import]

@Rob @Tom: Thanks again! I am relieved that non-assigned timers vanish. Now, I tried doing garbage collection each step of the way to see which statements make a difference, replacing the deprecated gcinfo() with collectgarbage("count").

[lua]function gc(num)
collectgarbage(“collect”)
print(num … ": " … collectgarbage(“count”))
end

function listener(event)
gc(3)
if event.count >= 2 then
timer.cancel(event.source)
gc(4)
event.source = nil
gc(5)
end
end

function test()
gc(1)
timer.performWithDelay(100, listener, 3)
gc(2)
end

test()[/lua]

My output was:
1: 119.046875
2: 119.6171875
3: 119.6796875
3: 119.6796875
4: 119.6796875
5: 119.6796875

If we are to trust these numbers, the cancelling and nilling makes no difference at all. How could this be? Doesn’t the actual memory cleanup happen before after the Lua parser exits the scope of the listener function, perhaps?
[import]uid: 73434 topic_id: 34463 reply_id: 139303[/import]

Hi Olav,
In my experience, printing out memory usage can be a bit misleading. Just doing a garbage collection doesn’t necessarily show an instant result… sometimes it takes several frame cycles to reflect the new value.

What I do, in testing memory, is to run a 1-second repeating timer of my memory printout, then I run my game and play each “stage” of it… open up the menu, load scenes, exit scenes, return to menu, restart the level, etc. While doing that, I look for “patterns” in the memory readout… typically there will be a value that you’ll begin to recognize. For example, when I go through 10-15 cycles of “load-exit-load-exit” or “restart-restart-restart”, one number will surface as a “common” value no matter how many times I repeat the cycle. When that happens, I’m quite convinced that I’m cleaning up everything properly… because the number is -exactly- the same over a dozen iterations of the gameplay.

Now, if the value kept gradually creeping up in value, then I’d suspect my memory was leaking somehow.

Brent [import]uid: 200026 topic_id: 34463 reply_id: 139341[/import]

I figured as much. Anyway, now that all the details about good memory habits are on the table, I can do the last bit of cleanup and submit my game. I also plan to write a document about my general experiences with Corona that I will send you guys. Thanks again! [import]uid: 73434 topic_id: 34463 reply_id: 139440[/import]

Hi Olav,
That would be greatly appreciated (a document from a direct developer standpoint about some memory cleanup procedures, testing methods, etc.). When you have it ready, just e-mail it to Rob or myself.

Thanks, and best of luck with it!
Brent [import]uid: 200026 topic_id: 34463 reply_id: 139483[/import]

@Rob: Some more things about the memory handling. I’m finishing up my game, and really want to get all of this right.

  • Reply #1, Question 1: When removing timers, transitions, audio, and runtime listeners, like you said, do you also have to set all these to nil?

  • Reply #1, Question 1: How are transitions and audio with “onComplete” are different from those who don’t have “onComplete”?

  • Reply #1, Question 1: You wrote “Anything that’s calling back to a function defined in your scene that references any of your display objects.” Could you give a concrete code example showing what you mean here?

  • Reply #3, Question 5b and 6: Still waiting for an answer here.

  • Question 7 (new): Widgets created with widget.newSomething() and added to a group, will they be completely deallocated when their parent group is deleted, or do they need some extra attention, widgets being a special type of object? [import]uid: 73434 topic_id: 34463 reply_id: 139034[/import]

5b. If I have just a few sound effects in my app that are used all around, can’t they simply live in the global scope of the app? (If I, on the other hand, had a large set of audio files in scene A, and a different large set of audio files in scene B, I would definitely see the need for deallocating audio after each scene).

Correct. I would go so far to say that even if you have a long clip that you use frequently, load it once in some form of global scope and keep it. You would probably pay a bigger performance penalty disposing and reloading it. But if you have sounds unique to a scene and you won’t be back there for a while (or ever) dispose them.

6. In the following code, do I need to nil both variables, or just one of them to cause deallocation?

Yes you must. I conducted a simple test using a variant of your code:

local a = display.newCircle(0,0,100)  
local b = a  
display:remove(a)  
a = nil  
print(b)  

And this was printed:

2013-01-15 18:20:35.333 Corona Simulator[465:707] table: 0x1007dda40  

So clearly B is holding onto the pointer and the garbage collector will think you are still retaining this memory and not de-allocate it.

- Reply #1, Question 1: When removing timers, transitions, audio, and runtime listeners, like you said, do you also have to set all these to nil?
Timers, yes. Transitions, if you save a reference to them, then yes you do. In other words:

myTrans = transition.to(a, {time=500, x=100, y=100, onComplete = checkit})  

myTrans will have a reference to the transition when your checkit() function runs, but the transition is over, so there is nothing to cancel so you should nil the pointer. If you cancel the transition, you are still left with a value in the pointer, and you need to nil that as well.

As for Runtime listeners, I don’t believe there is anything you can nil as you don’t ever get a pointer back from any of the add listener functions.

- Reply #1, Question 1: How are transitions and audio with “onComplete” are different from those who don’t have “onComplete”?
With regards to memory, I don’t believe there are any.

- Reply #1, Question 1: You wrote “Anything that’s calling back to a function defined in your scene that references any of your display objects.” Could you give a concrete code example showing what you mean here?
I’m not sure I would have all of them in memory. But as an example, you call network.download() to fetch a photo from Flickr and you give it a call back to then create the display object for it and shove it into your scene’s view. Before that photo downloads, your user navigates away and you purge the scene. That callback function is still active and the system will call that function and try to execute the code. If that function doesn’t exist or the group you’re putting the object into doesn’t exist you can expect a SIGSEGV segment violation or bus error crash.

- Question 7 (new): Widgets created with widget.newSomething() and added to a group, will they be completely deallocated when their parent group is deleted, or do they need some extra attention, widgets being a special type of object?
They should be completely dealocated just like a display.newImageRect() would be. [import]uid: 199310 topic_id: 34463 reply_id: 139092[/import]

@Rob: I am amazed at the precise answers you just provided. This was extremely useful in cleaning up our project. I think I have but one more question regarding timers. Let’s call this Question 8.

What I am basically trying to figure out, is it you have to assign all timers to variables in order to deallocate them, or if it’s quite the opposite, that you need to deallocate them if you have assigned them to a variable.

8a. The timer is not assigned to a variable. Will the timer take complete care of its own deallocation when it has finished running?

[lua]function listener(event)
end
timer.performWithDelay(1000, listener)[/lua]

8b. Again, the timer is not assigned to a variable. Is calling timer.cancel() from within the timer function sufficient to deallocate the timer (to stop it before its natural end)?

[lua]function listener(event)
if event.count > 5 then
timer.cancel(event.source)
–event.source = nil – is this necessary (or even meaningful?)
end
end
timer.performWithDelay(1000, listener, 10)[/lua]

8c. Now, the timer is assigned to a variable. I assume here that deallocation can be done with timer.cancel() and nil-ing both inside and outside the listener function?

[lua]local var
function listener(event)
timer.cancel(var)
var = nil
end
var = timer.performWithDelay(1000, listener)
timer.cancel(var)
var = nil[/lua]

PS! I assume how I should deallocate timers will also be applicable to transitions?
[import]uid: 73434 topic_id: 34463 reply_id: 139152[/import]

8a. The timer is not assigned to a variable. Will the timer take complete care of its own deallocation when it has finished running?

I don’t have a good way to test this, but since this method does not return a pointer for you to cancel and nil out, I would assume it would clear itself up but after trying to get a good read on it, I do see memory going up. I’m going to bring this up to the team.

8b. Again, the timer is not assigned to a variable. Is calling timer.cancel() from within the timer function sufficient to deallocate the timer (to stop it before its natural end)?

If you nil event.source, it appears that it’s sufficient to clear the memory.

function listener(event)  
 if event.count \> 5 then  
 timer.cancel(event.source)  
 --event.source = nil -- is this necessary (or even meaningful?)  
 event.source = nil  
 print(gcinfo())  
 collectgarbage("collect")  
 print(gcinfo())  
 end  
end  
  
collectgarbage("collect")  
print(gcinfo())  
timer.performWithDelay(100, listener, 10)  

results in:

2013-01-16 18:45:59.876 Corona Simulator[452:707] 117  
2013-01-16 18:46:00.667 Corona Simulator[452:707] 126  
2013-01-16 18:46:00.668 Corona Simulator[452:707] 117  

8c. Now, the timer is assigned to a variable. I assume here that deallocation can be done with timer.cancel() and nil-ing both inside and outside the listener function?
Most certainly if you return the timer into a variable and cancel it early, you should nil the pointer.
[import]uid: 199310 topic_id: 34463 reply_id: 139236[/import]