Unity Editor Scripting: Mesh Leakage

I haven’t been very active with my blog, but for once I made a discovery that might be worth sharing. Finally a proper tech post! Hooray!

If you ever got sucked in to the wonderful world of Unity editor scripting and tried to generate meshes outside the run-time mode, you might stumbled across this warning message in the console:

As a matter of fact, I think most of my customers – who bought the 2D vector graphics extension RageSpline – have had to live with this warning for a while now.

It is not a showstopper, though. Every time you save your scene, Unity checks if there are meshes that don’t belong to any gameobjects and gets rid of them. You get a warning and thats it. Life goes on.

BUT IT’S ANNOYING! And looks unprofessional. It’s like my Mrs. after I’ve done the vacuuming: She digs up a tiny piece of dirt somewhere and the nagging starts. Every fraggin’ time. I hate that. It ruins the best part of vacuuming, which is the ending part.

The reason for mesh leaking lies within the sharedMesh property. Every GameObject that has MeshFilter-component, has a sharedMesh property with it. Like the name implies, this is a mesh that is shared. But shared with who or what? Well, it’s shared with other GameObjects. And to be technically accurate: with other MeshFilter components.

When the user decides to make duplicate of an existing GameObject, what happens to the sharedMesh is the root of the problem. The sharedMesh property is automatically set to refer to the same mesh that the originating GameObject has. Now the old existing GameObject and the newly created duplicate both refer to the same mesh. While this is a proper behavior for most of the use-cases, it’s not for mine. I wan’t every mesh to be individual and not shared. Or maybe I want some of them shared, but not automatically.

But can’t I just grab the duplication event and handle this? Well, no. Unity doesn’t provide me an access to such an event. Or at least that is my conclusion after doing some extensive googling. Only place where this situation can be handled is in the Awake()-method. Awake() is a method that gets called every time the GameObject is generated. Whether it’s when you load the scene, instance an prefab or press play on you editor, Awake() gets called. Right now we are only interested when this happens in the edit-mode.

So the Awake() is called and Houston we have that problem. You know the problem I talked about: The vacuum cleaner and the nagging, and the… Oh wait… Back to the subject. We now know that the sharedMesh that we have on this GameObject might be referenced by some other GameObject too. Not always the case. Awake() might called for the scene load or dropping our script to empty GameObject. But it’s possible, that somewhere out there lies a GameObject, that is sharing my beatiful mesh with me. My precious.

To handle this perfectly, we need to stop guessing and find out for sure. The only way to know is to iterate through every other GameObject of this type and compare my sharedMesh with it. If a match is found, we generate a new Mesh instance for this. If not, we do absolutely nothing.

In the Awake():
1. Check that you are not in the run-time mode
2. Iterate through every GameObject of this type, expect myself
3. Check if there are any sharedMesh equals to my sharedMesh
4. If yes, generate a new sharedMesh instance via meshFilter.mesh = new Mesh()
5. If not, do nothing
6. Profit

Here is a crude example of how your Awake() might look. I’m also generating MeshFilter and MeshRenderer if they are missing.

    void Awake() {

        if (!Application.isPlaying)
            MeshFilter meshFilter = null;
            MeshRenderer meshRenderer = null;
            meshRenderer = gameObject.GetComponent("MeshRenderer") as MeshRenderer;
            if (meshRenderer == null)
                meshRenderer = gameObject.AddComponent("MeshRenderer") as MeshRenderer;

            meshFilter = gameObject.GetComponent("MeshFilter") as MeshFilter;
            if (meshFilter == null)
                meshFilter = gameObject.AddComponent("MeshFilter") as MeshFilter;

            if (meshFilter.sharedMesh != null)
                MyPreciousObject[] objects = GameObject.FindObjectsOfType(typeof(MyPreciousObject)) as MyPreciousObject[];

                foreach (MyPreciousObject myPrecious in objects)
                    MeshFilter otherMeshFilter = myPrecious.GetComponent(typeof(MeshFilter)) as MeshFilter;
                    if (otherMeshFilter != null)
                        if (otherMeshFilter.sharedMesh == meshFilter.sharedMesh && otherMeshFilter != meshFilter)
                            meshFilter.mesh = new Mesh();
            if (meshFilter.sharedMesh == null)
                meshFilter.sharedMesh = new Mesh();

I hope this really works, because I haven’t given it real-life testing yet, but it seems promising. If you know something about this matter that I’m missing, shoot me with an e-mail to juha [at] juhakiili.com


Juha Kiili
September 30th, 2011