Seb Lee-Delisle

Menu

Memory leaks in Papervision3D patched up!

We all know how stable and optimised Papervision3D is. But a small number of developers who need to constantly create and discard 3D objects will have encountered a particularly tricky little memory leak.

There have been several workarounds and custom destroy functions, which have helped to patch up the leak, but I can now announce that, with the help of the rest of the team, and the Papervision3D mailing list, I have finally fixed the problem once and for all! (subject to further testing 🙂 )

And the reason for the memory leak? Dictionary objects.

I’m sure most of you have heard of Dictionaries already but for those who haven’t, it’s like an array, except instead of index numbers to reference a value you use an object:

var myDict : Dictionary = new Dictionary();
myDict["hello"] = "greeting";
myDict[stage] = "stage";
myDict[42] = "number";
 
//outputs "greeting"
trace(myDict["hello"]);
//outputs "stage"
trace(myDict[stage]);
//outputs "number"
trace(myDict[42]);

OK, so that’s a pretty nasty example, but you get the point. Or if you don’t, my friend Grant, has explained it much more eloquently.

There’s a parameter in the constructor for a Dictionary; a Boolean called weakKeys that is false by default. This means that if you use an object as a key in the dictionary, the object will never be discarded by the garbage collector, even if all other references to that object are lost. If you set the flag to true, and you have no other references to the key object in your code, the garbage collector will destroy that object, and that key in the dictionary will be lost.

In Papervision, dictionaries are used to store the references between objects and their materials. So materials create a list of objects that they’ve been applied to. And the dictionary object for the material uses weakKeys so that if a DisplayObject3D is later discarded, that entry in the Dictionary will also be lost, and the Garbage Collector destroys it.

Or does it…?


Here’s how we were using the dictionary in MaterialObject3D, the base class for all Papervision3D materials (paraphrased) :

// in the constructor
objects = new Dictionary(true);
 
...
 
public function registerObject(obj : DisplayObject3D) : void
{
   // when a DisplayObject3D is registered with the material it's added
   // to the objects dictionary
   objects[obj] = obj;
}

So what do you notice about that? The key is also the value being stored for that key. Why would you want to do that? Well because if we want to then iterate through all the objects in the material we can use :

for each(var value : DisplayObject3D in objects)
{
   // value is our displayobject
   doSomething(value);
}

But the inherent problem with this method is that as long as there is an instance of our object stored in the Dictionary, the weakKeys functionality never kicks in. It only works if the object is used as a reference, but not if that object is actually getting stored within that dictionary item.

So the solution? Don’t store the object in the dictionary. Just use it as a key! So our rewritten registerObject function :

 
public function registerObject(obj : DisplayObject3D) : void
{
   // when a DisplayObject3D is registered with the material it's added
   // to the objects dictionary
   objects[obj] = true;
}

So you can see now that the value we’re storing is just a simple Boolean. In fact it doesn’t matter what we’re storing in there. It could be null, “hello” or any other value. So how do we then iterate through all the objects (that are now references) in our Dictionary? Use this slightly different for loop :

for(var ref : * in objects)
{
   // ref is our displayobject
   doSomething(ref as DisplayObject3D);
}

Notice that we’re using for(… in …) rather than for each (… in …). And this gives us the reference, not the value. And what’s even better, when you discard a DisplayObject3D and remove it from the scene, it’s now cleaned up by the Garbage Collector.

I committed the changes last night, so update and test it out for yourself. I’ve been using this test code which makes either a sphere, a cube (which uses a materials list) or a shaded sphere, every frame, adds it to the scene, renders it, and then discards it. Follow the memory management and you’ll see that there is no longer a memory leak!

It probably needs some more testing, especially with DAEs, but for now it seems that .

Here’s the test code :

 
package {
	import flash.display.BitmapData;
	import flash.events.Event;
 
	import org.papervision3d.cameras.CameraType;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.materials.BitmapMaterial;
	import org.papervision3d.materials.WireframeMaterial;
	import org.papervision3d.materials.shaders.GouraudShader;
	import org.papervision3d.materials.shaders.ShadedMaterial;
	import org.papervision3d.materials.utils.MaterialsList;
	import org.papervision3d.objects.primitives.Cube;
	import org.papervision3d.objects.primitives.Sphere;
	import org.papervision3d.view.BasicView;
	import org.papervision3d.view.stats.StatsView;
 
 
	public class GCTest extends BasicView
	{
		private var cube : Cube;
		private var sphere : Sphere;
 
		public function GCTest()
		{
			super();
 
			addChild(new StatsView(renderer));
 
			// uncomment the one you want to test
			addEventListener(Event.ENTER_FRAME, testSphere);
			//addEventListener(Event.ENTER_FRAME, testCube);
			//addEventListener(Event.ENTER_FRAME, testShader);
 
		}
 
		public function testCube(e:Event) : void
		{
 
			if(cube)
			{
				scene.removeChild(cube);
 
			}
 
			var ml : MaterialsList = new MaterialsList();
			ml.addMaterial(new WireframeMaterial(Math.random()*0xffffff,1), "all");
			cube = new Cube(ml,100,100,100,5,5,5);
			scene.addChild(cube);
 
			singleRender();
 
		}
 
		public function testSphere( e: Event) : void
		{
			if(sphere)
			{
				scene.removeChild(sphere);
			}
 
			sphere = new Sphere(new WireframeMaterial(Math.random()*0xffffff,1));
			scene.addChild(sphere);
			singleRender();
 
		}
 
		public function testShader(e : Event) : void
		{
			if(sphere)
			{
				scene.removeChild(sphere);
 
			}
 
			var light : PointLight3D = new PointLight3D();
			light.x = 300;
			light.y = 300;
 
			var shader : GouraudShader = new GouraudShader(light, 0xFFFFFF,0x404040)
 
			var mat : BitmapMaterial = new BitmapMaterial(new BitmapData(100,100,false,Math.random()*0xffffff));
 
            var shadedmaterial:ShadedMaterial = new ShadedMaterial(mat, shader);
 
			sphere = new Sphere(shadedmaterial);
			scene.addChild(sphere);
			singleRender();
		}
 
 
	}
}
This entry was posted in Obsolete, Papervision3D. Bookmark the permalink.

28 Responses to Memory leaks in Papervision3D patched up!