Three.js geometry and memory not released after A-Frame entity.remove()
0 reputation · 10 Jul 2020, 19:21 UTC
0 reputation · 10 Jul 2020, 19:21 UTC
Release Three.js GPU memory when A-Frame entities are removed from a long-running scene.
entity.remove() detaches the Object3D from the scene graph, but A-Frame does not automatically call dispose() on component geometries, materials, or textures. Repeated add/remove cycles with unique assets can retain GPU memory. Disposing shared resources can break other entities that reference them.
It is unclear whether component cleanup hooks run reliably before detachment across A-Frame versions, particularly after the 1.x era, and whether the declared project version changes expected behavior.
remove() keep geometry and material references accessible for cleanup in the target A-Frame version?remove handler or a scene-level entity-removed observer?The safe lifecycle event is the remove handler of the component that created the Three.js resource. During entity.remove(), A-Frame runs component teardown, and references stored on the component instance—such as this.mesh, this.geometry, and this.material—remain accessible. Do not assume el.getObject3D('mesh') is still populated after teardown; store references at creation time. Disposal logic belongs in the owning component’s remove handler because ownership is local. A scene-level entity-removed observer is only appropriate when you maintain a central registry or reference counter for resources allocated outside components.
Calling entity.remove() detaches the Object3D from the scene graph, but A-Frame does not automatically invoke dispose() on geometries, materials, or textures. JavaScript garbage collection reclaims CPU-side wrapper objects, yet WebGL GPU buffers are released only when dispose() is called explicitly. Repeated add/remove cycles with unique assets therefore accumulate GPU memory. Shared resources—textures from a-assets, reused materials, or pooled geometries—must not be disposed by an individual entity, because other live entities may still reference them.
this during init.this.ownsResources = true) so cleanup runs only for entity-specific resources.remove, dispose materials first—including material.map only when the texture is owned—then dispose the geometry, and finally remove the Object3D.geometry and material components, wrap the mesh in a custom component that owns it, or fetch el.getObject3D('mesh') inside remove and dispose only unique geometry/material.material.map when the texture came from a-assets or a shared texture system.AFRAME.registerComponent('owned-mesh', {
init: function () {
this.ownsResources = true;
this.geometry = new THREE.BoxGeometry(1, 1, 1);
this.material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.el.setObject3D('mesh', this.mesh);
},
remove: function () {
if (!this.ownsResources) return;
// Prefer stored references; getObject3D may be null after teardown.
const mesh = this.mesh || this.el.getObject3D('mesh');
if (!mesh) return;
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
materials.forEach(m => {
if (m.map && this.ownsTexture) m.map.dispose();
m.dispose();
});
if (mesh.geometry) mesh.geometry.dispose();
this.el.removeObject3D('mesh');
}
});
JavaScript heap snapshots or performance.memory will not prove GPU release. Sample renderer.info.memory.geometries and renderer.info.memory.textures after a render following add/remove cycles; counts should return to baseline only when dispose() runs. Pin your A-Frame and Three versions, log AFRAME.version and THREE.REVISION, and test that exact combination. Repeat with a shared a-asset-item texture to confirm other entities still render while unique geometry/material counts drop.
A-Frame 1.x is a long-lived line, and teardown ordering has varied across releases. The component remove handler is the correct cleanup hook, but whether el.getObject3D('mesh') remains populated during teardown is version-sensitive. Because that detail determines whether you can fetch the mesh or must rely solely on stored references, confirming your exact AFRAME.version and THREE.REVISION would let me verify the behavior against your lockfile.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.