AS3 memory leak prevention

Ran into a really nice memory leak the other day, it caused me to lose about a day’s work correcting my code to handle clean-up in a better way. The FP11 Garbage Collection routines will handle most of the work for you, but a few changes to code can go a long way.

Understanding the FP11 GC model’s inner workings is quite valuable, I have posted a link to a great article on the subject. Here are some of the practices I use to avoid these kinds of problems.

 
1. Destroy your class

AS3 programmers seem to rarley use destructors in their classes, different data types give you different options to hook into a “destructor” event. Any display object that gets added to your stage will allow you to track AddedToStage and RemovedFromStage events, thus giving you access to hooks to clean up your class. Here is an example:

package {

	import flash.display.Sprite;
	import flash.events.Event;

	public class MyClass extends Sprite {

		private var _box:Sprite;

		function MyClass():void {
			_box = new Sprite();
			_box.graphics.beginFill(0x000000);
			_box.graphics.drawRect(20,20,200,200);
			_box.graphics.endFill();

			addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);
			addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true);

			addChild(_box);
		}

		private function onAddedToStage(evt:Event):void {
			trace("I have just been added!");
			// do something...
			removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}

		private function onRemovedFromStage(evt:Event):void {
			trace("I'm cleaning up now!");
			trace("My property before cleanup: "+_box);
			trace("My number of children before cleanup: "+this.numChildren);
			// clean-up
			removeChild(_box);
			_box = null;

			trace("My property after cleanup: "+_box);
			trace("My number of children after cleanup: "+this.numChildren);
			removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
		}

	}

}

The instancing code:

import MyClass;

var myobject:MyClass = new MyClass();
this.addChild(myobject);
this.removeChild(myobject);

This class effectivly cleans up after itself, it removes it’s assigned listeners once they have completed and removes all of it’s child display objects. Then by assigning the _box property to null we are telling the garbage collection that it is ok to collect this before destroying the class.

Here is another example that is not a DisplayObject.

package {

	public class MyClass {

	private var _data:Object;

		function MyClass() {
			_data = new Object();
			_data.myData = "a test string";
		}

		public function destroy() {
			trace("cleaning up...");
			trace("My property _data.myData before cleanup:"+_data.myData);
			delete(_data.myData);  // this will work because myData is a dynamically created property!
			trace("My property _data.myData after cleanup:"+_data.myData);
			_data = null;
			trace("My property _data after cleanup:"+_data);
		}

	}

}

Instancing code:

import MyClass;

var myobject:MyClass = new MyClass();
// do some stuff
// now i'm done. clean up
myobject.destroy();

This class depends on you to call the destroy method but the extra work is worth it because the class won’t get missed by the FP11 GC when the time comes.

2. It’s good to be weak

Events and event listeners are intrinsic to powerful as3 applications, you can still write a decent application with minimal events but there are LOTS of problems that can be solved easily by using them. One of the most common memory leaks is not properly removing event listeners or not using weak references in your listeners.

for example, take this event listener:

addEventListener(MouseEvent.CLICK, onClickHandler);

though this will work correctly, unless you explicitly call removeEventListener() then the listener will always be attached to the class that it exists in. and worse yet may not get flagged for cleanup by the GC once it is not used anymore. a quick fix is to use “weak references”, basically it tells the FP11 GC that once no reference is found for this listener then ALWAYS clean it up. This is a great safety in case you forget to call removeEventListener. Custom events will benefit greatly from using weak references.

Same event but with a weak reference:

addEventListener(MouseEvent.CLICK, onClickHandler, false, 0, true);

The extra 3 arguments are “bubbles”, “priority” and “use weak reference”, by telling the event not to bubble up the call chain, have a low priority and use a weak reference we save ourselves from forgetting to clean up. Still a removeEventListener() call should be made just for good practice’s sake. ;)

3. Even locals have die

Good cleanup practices are essential in your objects, but often cleanup in your object’s methods can be just as crucial. These types of problems don’t cause memory leaks that often (but they can!), but they do contribute to performance bottlenecks. Let’s take this method for example.


private function doSomething():Array {
     var output:Array = new Array()

     for(var i:int=0; i < 30; ++i) {
          var obj:Object = new Object();
          obj.id = i;

          // add to array
          output.push(obj);
     }

     return output;
}

This method doesn't do anything important but the question is how can we save memory while it runs? One idea is to kill the obj variable after reach loop, thus ensuring that we aren't adding anything useless to the stack. Here is the new example.


private function doSomething():Array {
     var output:Array = new Array()

     for(var i:int=0; i < 30; ++i) {
          var obj:Object = new Object();
          obj.id = i;

          // add to array
          output.push(obj);

          // clean up
          delete(obj.id);
          obj = null;
     }

     return output;
}

This example always cleans up it's local variables. This optimization is not always necessarily but is good practice.

So these are a few of the ways that I use to ensure that I avoid memory leaks in my applications. Have fun, happy, memory leak free coding!

Away3D 4 Importing And Exporting

So far I have taken somewhere in the range of 5 days working with various Maxscripts to handle exporting into Away3D 4, there are many options for different formats. Some with better results then others.

Good old 3DS format
Everything seems to accept 3DS these days, yet even though Away3D supports this format they do not support the key-framed  animation contained inside of the 3DS format. This makes it probably the simplest format for non-animated props.

MD5 format
I have gotten this format to work, but with a few caveats, the only exporter I found that works fails if there is no skeletal data for the mesh you are exporting. This makes MD5 only good for character animation or animation that uses bones and is to be controlled inside of your engine, like a crane for example.

The exporter I am using can be found here, MaxMD5 exporter.

AWD1 and AWD2 formats
These are Away3D formats, AWD1 is the currently defined format and is a ASCII AS3 class that supports Key-framed animation, textures, static mesh and is viewable via prefab. The AWD2 format specification is not complete so no exporter is available as of yet. I will post a version once it is.

The exporter can be found here: 3DMax AWD1 Exporter.