Garbage Collection
From Director Online Wiki
Director does its best to clear obsolete data out of the computer's Random Access Memory (RAM), in order to make space for new data. This is generally known as Garbage Collection. In lower-level languages such as C++ you have to clean up the memory yourself.
However, there are situations that Director is unable to deal with. If you let such a situation develop, your movie will leak memory. Any application that leaks even infinitessimal amounts of memory will ultimately crash, possibly crashing your computer at the same time. It is thus essential that you know how to write your code in such a way that Director is able to collect any garbage automatically.
Garbage Collection is concerned with Lingo objects, such as lists and instances of Behaviors and Parent scripts. Director has a related unload? facility for removing media items from memory.
| Table of contents |
How does Garbage Collection work?
To understand Garbage Collection, you need to know how Director refers to objects, and how it keeps a count of references to objects.
Pointers
In Lingo, objects are referred to by pointers. Technically, a pointer is the address in RAM space where the information concerning the object is stored. You may be familiar with the me pointer used in most behaviors. Here's what a pointer looks like when you display it in the Message window:
-- Create a Parent Script vScriptMember = new(#script) vScriptMember.scriptType = #parent vScriptMember.name = "Parent Script" vScriptMember.scriptText = "--" -- so that the script will compile vScript = script(vScriptMember)
-- Now create an instance of it put vScript.new() -- <offspring "Parent Script" 1 7ccd5c0>
The output <offspring "Parent Script" 1 7ccd5c0> indicates that:
- An instance of the script "Parent Script"
- is stored at the memory address 7ccd5c0
- and there is only 1 pointer referencing this memory address.
If you create a variable and store the new instance in that, you will see that there are now two pointers to the instance:
vInstance = vScript.new() put vInstance -- <offspring "Parent Script" 2 7ccc320>
Notice that the new instance is stored at a different memory address from the earlier one, and that it now has two pointers referencing that address.
Why two pointers? Well, the Message window needs one pointer in order to be able to obtain information such as the name of the parent script, and the variable vInstance holds another.
Clearing out objects with no external pointers
When Director detects that the number of pointers has dropped to zero, it performs its Garbage Collection. In other words, it tells the Operating System: "I don't need the chunk of memory that starts at 7ccd5c0 any more. You can put something else there if you want."
In the case of the first instance of script ("Parent Script"), once the Message window had finished displaying the information concerning the instance, it no longer required a pointer to the instance, and Director cleared the instance from memory.
Lists
Lists are also objects. The put command is handled differently by lists than it is by instances. Instead of displaying the pointer, the put command will display the contents of the list:
vList = [] vList.append(vScript.new()) put vList -- [<offspring "Parent Script" 1 7c19950>]
This time, the Message window needs a pointer only to the list, not to the script instance. As a result, the script instance is shown with only a single pointer referencing it.
Where is that pointer? It is stored in vList. You can access this latest instance of the script "Parent Script" by using the following syntax:
put vList[1] -- <offspring "Parent Script" 2 7c19950>
The Message window now creates a temporary pointer to the script instance, prints out the information it is told to find there, and then discards the temporary pointer.
Reciprocal pointers
It is very easy to create a reference to an object within the object itself. If you edited the scriptText of script "Parent Script" so that it looked like this...
-- DO NOT TRY THIS AT HOME -- property pPointer
on new(me) pPointer = me end
... you could create a script instance that always has at least one pointer (pPointer) referencing it. Director would never see the number of pointers to the instance fall to zero, and would therefore never clear the instance from memory.
Notice that the new handler does not return the me pointer to any external handler, so there is no way to set an external variable to point to the new instance. In other words, this situation creates an inaccessible instance that Director cannot collect as garbage. This simplistic example shows you how memory can be leaked. Let's now look at a more realistic situation. In realistic situations, reciprocal pointers can be much harder to spot.
Example
Here's a simplified extract of the Radio Button Group behavior from the Behavior Library Palette. The purpose of this extract is to allow all radio buttons to share a list (ourGroupList) of all the Radio Button Group instances. When the user clicks on one radio button, the behavior on that button will tell all the others to switch themselves off.
property ourGroupList
on beginSprite(me) ourGroupList = [] sendAllSprites(#RadioGroup_RollCall, ourGroupList) end beginSprite
on RadioGroup_RollCall(me, aGroupList) ourGroupList = aGroupList ourGroupList.append(me) end RadioGroup_RollCall
When the beginSprite handler has been run, there will be two pointers to the Radio Button Group instance:
- one on the sprite's scriptInstanceList
- one in the shared ourGroupList
When the user moves to a frame where the Radio Button sprite no longer exists, the pointer to its behavior on the scriptInstanceList will be removed automatically by Director. But the pointer on ourGroupList will remain, unless you remove it manually.
on endSprite(me) ourGroupList.deleteOne(me) -- without this line a memory leak will occur end
Putting out the Garbage
Nobody likes to take out the garbage. But it has to be done.
There are no automatic techniques for tidying up such reciprocal pointers. You must deliberately plan for destroying reciprocal pointers on a case-by-case basis. It is a good idea to get into the habit of creating the handler to destroy reciprocal pointers at the same time as you create the property declaration for the pointers in question.
On Mac Classic and on Windows, you can use the freeBytes to report how much memory is available to your application. (On Mac OS X, the freeBytes always returns the maxInteger). If you see the value of the freeBytes falling steadily while there is no interaction or background activity, or after the end of a cycle of activity, the chances are that garbage is accumulating. You can see a simple example of this here.
See also
JavaScript _system.gc()