A common question in the forums and elsewhere is how to properly and thoroughly clean up scenes when using a scene manager like Storyboard or Director.

You need to understand how modules behave in Lua and how that impacts Storyboard. When you do…

…somewhere in the process the following code executes:

In Lua, if you load a module multiple times, only one copy is loaded, and it stays in memory until it’s specifically un-required. This is why we can have persistent values that survive between scenes. Storyboard scenes are modules that have a main chunk and several functions that are called by dispatched events. If your scene module looks like this:

You can see with the comments above which parts of the scene are executed at certain times. It’s important to understand that variables that live in the main chunk are rarely cleared during the execution of your code.

Objects and Groups

A common practice in Storyboard or Director is to organize your display objects — sprites, UI elements, etc. — into display groups that are under the “umbrella” display group for the particular scene. With Storyboard, your code might look like:

What’s happening is the display.[API] functions create a table and an allocated chunk of memory associated with it to hold the image to be displayed. This table contains a pointer to that memory as well as function references to things like removeSelf(), setReferencePoint(), etc. (the various methods and properties that make it a display object).

When you call object:removeSelf(), the memory is marked for deletion, and when you nil the variable, the Lua garbage collector will free up the memory in short order — assuming, of course, that you do not have other references to the object locking up the memory chunk!

Both of these scene managers remove their “umbrella display group” when disposing the scene. They do so by calling removeSelf() on the group. This iterates over the objects (and sub-groups of objects) in the group and frees the texture memory. It also sets these to nil so the Lua memory can also be freed. Likewise, any event listener that is “attached” to an object is removed.

However, in our example, the table doSomething still holds references to everything that was the object. That little bit of memory is still allocated, but you do not need to worry about it.

Be Careful…

Local references to functions are nil’ed out when the function ends. That is, they only live for the time that the function is executing. These will be cleaned up for you.

Where you can create a small memory leak is if you do this:

Similar to the first example, the enemies will all be cleaned up when Storyboard destroys the scene, however the “enemy” variable will stick around until the scene is “un-required.”

The table returned by spawnEnemy() is cleaned up because the group retains a reference to it as a remnant copy of the last enemy allocated. Of course if you re-enter the scene, this block will continue to persist, it probably should get nil‘led out, but its not that much memory and it’s not going to grow.

The Trouble With Global Variables

You frequently hear that global variables are bad. Memory leaks caused by global variables, in particular with a scene manager like Storyboard, are particularly problematic. Consider the code we’re working with:

In this example, we’ve made background and doSomething global by taking the “local” off of them. Similar to what’s happening in the main chunk, if this scene gets re-created, background and doSomething will overwrite their previous table with new tables, and some of that memory can’t be regained. The group still holds a reference to the object so the texture memory will be reclaimed, but the Lua memory will not. If you use similar code in multiple scenes, that background variable will be overwritten multiple times.

What About Event Listeners?

There are two types of event listeners. One type is attached to display objects and the other is attached to the global Runtime.

When you create an object and add a touch handler to it, you’re attaching a block of code via reference to the table. That is, you’re not adding the code to the table, but a pointer to the code. Code is in its own memory and it’s not something you allocate and dispose of. The code persists and there’s only one copy of it.

Once an object is removed, there is no way to trigger events against it, so in effect the event handlers are removed for you. You do not have to remove these if they are attached to an object that’s in a scene-managed group.

Runtime listeners, on the other hand, must be removed because they’ll continue to run after the scene is taken out of memory and the functions that are executing are likely going to reference an object that is no longer around. This can cause your app to crash.

Audio, Transitions, and Timers

Audio needs special handling with Storyboard and other scene managers.  You are responsible for freeing up the memory for audio with the audio.dispose() API call.  You have three options to load sound within your Storyboard scenes.

  1. In the main chunk.
  2. In the scene:createScene() phase.
  3. In the scene:enterScene() phase.

Of course, you could also load them in their own module or in main.lua. Each of the three methods have various consequences that you need to be aware of:

1. Loading in the main chunk:

Since this will likely execute just once, if you do audio.loadSound() or audio.loadStream() calls here, you can’t really dispose of them in the exitScene() phase or the destroyScene() phase since calling the scene again won’t execute the main chunk. As such, there’s no good way to know when the scene is being un-required, so you can dispose of the sounds in advance.

2. Loading in createScene():

This is a logical place to set up sounds and audio streams. Just remember that if you load them here, you must dispose of them in the destroyScene() function. The createScene() and destroyScene() events go together as a pair. If a scene gets purged or removed, destroyScene() is called, and the next time the scene is entered, createScene() is called again to re-create all the assets.

3. Loading in enterScene():

Since enterScene() can be called multiple times without createScene() getting called, you need to dispose of the audio in enterScene()‘s partner, exitScene(). This keeps your audio.loadSound and audio.dispose() sounds balanced.

Transitions must also be cancelled, since clearing a scene (and its objects) that contain “unresolved” transitions can cause the transitions to get stuck in memory. If you only have a few transitions isolated to a few objects, a convenient method to cancel these is to attach them as a property of the object itself. Then, when you clear the scene, you simply check if the object has a transition on it — if so, cancel it and nil out the object.

Finally, timers must be manually removed as well. If, upon completion, a timer references object(s) that are no longer in existence because they were cleared with the scene’s display group, an error will result.

“Memory Creep”

Monitoring memory can be a tricky business. One useful method is to run a memory-checking function on a repeating timer as follows (you can comment out the timer which triggers it when you don’t want to test):

IMPORTANT: With this function, the important thing to watch for is “memory creep.” That is, a steadily-increasing memory footprint that continues to climb (creep) upward as you navigate from scene to scene, back to the original scene, etc. Even if you move among three scenes as in A → B → C → A → B → C, memory will not always return to the exact same value it was when you started in scene A and then later return to scene A. This is not necessarily reason for concern. More concerning is if the memory continues to climb steadily as you navigate back to the same scene time after time after time. In that case, you most likely have a memory leak and you should resolve where it’s coming from!

In Summary…

This tutorial is a quick summary of memory cleanup practices, especially when using a scene manager. Proper memory management and cleanup is one of the keystones of app development, and we can’t emphasize enough that this crucial task should be treated with great respect from the very start, up to the final line in your code.

Original link:

Cleaning Up Display Objects and Listeners