Categories
General

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();
		}


	}
}

28 replies on “Memory leaks in Papervision3D patched up!”

Great! will test later as it affected my last project which I used the work around by adding “virtualMouse.stage = null” to destroy() method in “InteractiveSceneManager.as”…

Thanks guys!

@xero it’s a good technique, as long as you only use the value you’re storing as a key! In the way we’re now doing.

@Ed, let me know how it works with ISM

@Richard, ah yes I’m guessing that was due to the particle materials. Let me know if you’re still having problems.

Hi Seb,

I have just tried with re796 and the system memory still keeps increasing if I don’t add“virtualMouse.stage = null”in ISM… Basically I load/unload the swfs that contain 3d cube/materials and force garbage collection when unloading.

cheers

[…] we can read on the Seb Lee blog, the Papervision3D team have just committed a fix that enables the Garbage Collector to tidy up […]

[…] největší změny poslední doby jsou rozhodně. Sebův memory leak bug fix (za který mohlo definovaní vztahu objekt <> materiál pomocí Dictionary) a opět jeho […]

[…] found this post over at Seb Lee-Delisle’s blog so i’ve updated my PV source and revised my code but it doesn’t make any […]

I’ve got a memory leak in a current app im building – see here for more info:

deceptiveresolution.wordpress.com/2009/02/04/help-papervision-2-memory-leaks/

Hi Seb,

I think i’ve made some progress with the memory leak on my part but there still seems to be an issue within PV (in my app anyway) which you already know about. I think the problem i addressed is to do with materials not being tidied up so maybe it’s the same thing within PV. Out of interest is there anywhere i can submit a bug? There seems to be an issue when using MaterialList.clone() whereby if i’m cloning a list with interactive materials after cloning they are no longer interactive.

Any ideas???

cheers
Doug

Hey there I am also experienceing a memory leak. I am trying to use the zavoo graphics3D package to create a lathe object. If I run a test script like yours but substitute the sphere for a lathed object the memory just keeps on going up.

Any ideas?

Hmmm actually I seem to have resolved the problem. I was creating a single point light and flat-shade material in my constructor and applying this same material to each instance of the lathe object. However if I create the pointlight and flatshader along with the object as like in your example it seems to work fine?

Hi,
I am using flex profiler to track memory leak problem, and came across this memory leak due to Dictionary Object usage in papervision. Where can I get the latest papervision swc with the patch? I am using Papervision3D_2.0.883.swc for now.

bullshit, this doesn’t solve the memory leaks in papervision at all.. this materialsManagerDictionary is growing and growing… even when unregister material is called, (material.destroy()) doesn’t work for me at all, as I use shaded materials.

hi,
what is the best way to deal with multiple cube creation?
the following is my code, to put mulitiple cubes in an array:
but i find the memory still leaks over time…

public function testCube(e:Event) : void
{

for (var i = 0; i < 50; i++)
{
if(cubeArray[i])
{
scene.removeChild(cubeArray[i]);
}
var ml : MaterialsList = new MaterialsList();
ml.addMaterial(new WireframeMaterial(Math.random()*0xffffff,1), "all");
cubeArray[i] = new Cube(ml, 100, 100, 100, 5, 5, 5);
cubeArray[i].x = 100 * Math.random();
cubeArray[i].y = 100 * Math.random();
scene.addChild(cubeArray[i]);
}
singleRender();

}

Comments are closed.