Disposing loaded model

Hi, I’ve implemented my own method to dispose a loaded model (THREE.Group) form a .glb file.
To me it looks fine but I’d like someone to check if it’s the correct way to dispose resources or if I’m missing something?

    unload(target:THREE.Object3D){
        target.removeFromParent();
        target.traverse((child:any) => {
            // disposing materials
            if (child.material && !child.material._isDisposed){
                // disposing textures
                for (const value of Object.values(child.material) as any[]){
                    if (!value) continue;
                    if (typeof value.dispose === "function" && !value._isDisposed){
                        value.dispose();
                        value._isDisposed = true;
                    }
                }
                child.material.dispose();
                child.material._isDisposed = true;
            }
            // disposing geometries
            if (child.geometry?.dispose && !child.geometry._isDisposed){
                child.geometry.dispose();
                child.geometry._isDisposed = true;
            }
        });
    }

EDIT:
Here is the Garbage Collector friendly version :slight_smile:

    unload(target:THREE.Object3D){
        target.removeFromParent();
        target.traverse((child:any) => {
            // disposing materials
            if (child.material && !child.material._isDisposed){
                // disposing textures
                for (const [key, value] of Object.entries(child.material) as any[]){
                    if (!value) continue;
                    if (typeof value.dispose === "function" && !value._isDisposed){
                        value.dispose();
                        value._isDisposed = true;
                        child[key] = null;
                    }
                }
                child.material.dispose();
                child.material._isDisposed = true;
                child.material = null;
            }
            // disposing geometries
            if (child.geometry?.dispose && !child.geometry._isDisposed){
                child.geometry.dispose();
                child.geometry._isDisposed = true;
                child.geometry = null;
            }

            // disposing skinned mesh
            if (child.skeleton?.boneTexture && !child.skeleton?.boneTexture._isDisposed){
                child.skeleton.boneTexture.dispose();
                child.skeleton.boneTexture._isDisposed = true;
                child.skeleton.boneTexture = null;
            }

            requestAnimationFrame(() => child.children = null);
        });
    }

missing child.skeleton.boneTexture.dispose(); and you all set :+1:
but if you never use skinned mesh, you can skip this.

1 Like

It looks good, however I’m not greatly qualified to say this—as I’ve just recently setup my own similar glb/gltf unloader. I also remove elements from the scene e.g. scene.remove(model) and use dispose e.g. renderer.dispose(). Basically I take out everything out of an abundance of caution after experiencing memory leaks.

I have global variables on hand to point loaded models to both remove it from scene and set it to null e.g. let modelPointer >> load model and have modelPointer = model >> unloading function with scene.remove(modelPointer) >> modelPointer = null >> load another model.

I recommend having the global variables as it was the only way I could stop my own memory leak from loading models without refreshing the page.

I would love to hear from others how they unload models, if there’s a ‘correct’ (or better) way?

3 Likes

Yes, totally agree/support this way too, the paranoia path :smile: I didn’t wanted to be picky and talk about NULL . But I think it’s more relevant than ever. Since three.js objects become more complex each release, it’s indeed a good move to facilitate browser’s garbage collector feature ( GPU is not the only culprit)

3 Likes

Browsers are smart enough to garbage collect any unreferenced resource. The most important thing is to not keep any reference/variable pointing to your assets. If you keep around any pointer it won’t be disposed even when calling object.dispose().

1 Like

@Fennec I thought the browser would do this until I created a model loader that would eventually max out allocated memory (and crash all browsers I tested, mobile and desktop) after loading enough models without applying the method I mentioned above. Before this incident I’d never even worried about memory usage, it confused me for days what the problem was because the surface-level performance was fine. Nonetheless it encouraged me to improve the structure of my code and make all my functions tight and modular :slight_smile:

1 Like