three.js: Memory leak with TransformControls
Description of the problem
When using TransformControls with angular references are left after dispose of component. TransformControls.dispose() only releases listener but not any geometry or material associated with it so gc can’t release memory (https://stackoverflow.com/questions/33152132/three-js-collada-whats-the-proper-way-to-dispose-and-release-memory-garbag/33199591#33199591)
It was even hard to find all references as it misused Object3D and not Group for …XY/XYZ/XYZE…
What I did so far:
public static disposeTransformControl(node: THREE.Object3D) {
if (node === undefined || node.children === undefined) return;
for (let i = node.children.length - 1; i >= 0; i--) {
let child = node.children[i];
//child.userData = null;
this.disposeTransformControl(child);
const c = child as any;
if (c.E) {
this.disposeTransformControl(c.E);
this.disposeNode(c.E);
c.E = null;
}
if (c.XYZ) {
this.disposeTransformControl(c.XYZ);
this.disposeNode(c.XYZ);
c.XYZ = null;
}
if (c.XY) {
this.disposeTransformControl(c.XY);
this.disposeNode(c.XY);
c.XY = null;
}
if (c.XYZE) {
this.disposeTransformControl(c.XYZE);
this.disposeNode(c.XYZE);
c.XYZE = null;
}
if (c.XZ) {
this.disposeTransformControl(c.XZ);
this.disposeNode(c.XZ);
c.XZ = null;
}
if (c.YZ) {
this.disposeTransformControl(c.YZ);
this.disposeNode(c.YZ);
c.YZ = null;
}
if (c.planes) {
this.disposeTransformControl(c.planes);
this.disposeNode(c.planes);
c.planes = null;
}
if (c.activePlane) {
this.disposeTransformControl(c.activePlane);
this.disposeNode(c.activePlane);
c.activePlane = null;
}
if (c.pickers) {
this.disposeTransformControl(c.pickers);
this.disposeNode(c.pickers);
c.pickers = null;
}
if (c.handles) {
this.disposeTransformControl(c.handles);
this.disposeNode(c.handles);
c.handles = null;
}
if (c.handleGizmos) {
if (c.handleGizmos.E) {
for (const ar of c.handleGizmos.E) {
this.destructHandlerPicker(ar);
}
}
if (c.handleGizmos.X) {
for (const ar of c.handleGizmos.X) {
this.destructHandlerPicker(ar);
}
}
if (c.handleGizmos.Y) {
for (const ar of c.handleGizmos.Y) {
this.destructHandlerPicker(ar);
}
}
if (c.handleGizmos.Z) {
for (const ar of c.handleGizmos.Z) {
this.destructHandlerPicker(ar);
}
}
if (c.handleGizmos.XY) {
for (const ar of c.handleGizmos.XY) {
this.destructHandlerPicker(ar);
}
}
if (c.handleGizmos.YZ) {
for (const ar of c.handleGizmos.YZ) {
this.destructHandlerPicker(ar);
}
}
if (c.handleGizmos.XZ) {
for (const ar of c.handleGizmos.XYZ) {
this.destructHandlerPicker(ar);
}
}
if (c.handleGizmos.XYZ) {
for (const ar of c.handleGizmos.XYZ) {
this.destructHandlerPicker(ar);
}
}
if (c.handleGizmos.XYZE) {
for (const ar of c.handleGizmos.XYZE) {
this.destructHandlerPicker(ar);
}
}
this.disposeTransformControl(c.handleGizmos);
this.disposeNode(c.handleGizmos);
c.handleGizmos = null;
}
if (c.pickerGizmos) {
if (c.pickerGizmos.E) {
for (const ar of c.pickerGizmos.E) {
this.destructHandlerPicker(ar);
}
}
if (c.pickerGizmos.X) {
for (const ar of c.pickerGizmos.X) {
this.destructHandlerPicker(ar);
}
}
if (c.pickerGizmos.Y) {
for (const ar of c.pickerGizmos.Y) {
this.destructHandlerPicker(ar);
}
}
if (c.pickerGizmos.Z) {
for (const ar of c.pickerGizmos.Z) {
this.destructHandlerPicker(ar);
}
}
if (c.pickerGizmos.XY) {
for (const ar of c.pickerGizmos.XY) {
this.destructHandlerPicker(ar);
}
}
if (c.pickerGizmos.YZ) {
for (const ar of c.pickerGizmos.YZ) {
this.destructHandlerPicker(ar);
}
}
if (c.pickerGizmos.XZ) {
for (const ar of c.pickerGizmos.XZ) {
this.destructHandlerPicker(ar);
}
}
if (c.pickerGizmos.XYZ) {
for (const ar of c.pickerGizmos.XYZ) {
this.destructHandlerPicker(ar);
}
}
if (c.pickerGizmos.XYZE) {
for (const ar of c.pickerGizmos.XYZE) {
this.destructHandlerPicker(ar);
}
}
this.disposeTransformControl(c.pickerGizmos);
this.disposeNode(c.pickerGizmos);
c.pickerGizmos = null;
}
this.disposeNode(child);
node.remove(child);
child = null;
}
}
Code is quick and dirty solution not meant as perfect solution!
What would be the right way to solve this?
Thanks -Michael
Browser
- All of them
- Chrome
- Firefox
- Internet Explorer
OS
- All of them
- Windows
- macOS
- Linux
- Android
- iOS
Hardware Requirements (graphics card, VR Device, …)
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 17 (10 by maintainers)
Commits related to this issue
- Added geometry and material dispose to TransformControls.dispose(). Fixes #15743 — committed to arodic/three.js by arodic 5 years ago
- Added geometry and material dispose to TransformControls.dispose(). Fixes #15743 — committed to arodic/three.js by arodic 5 years ago
- Added geometry and material dispose to TransformControls.dispose(). Fixes #15743 — committed to arodic/three.js by arodic 5 years ago
- fix (#1) * Reset branch to dev * Simplified paths loop in webgl_loader_svg.html * Utils: Clean up * Update scene cameras to be shown in a window * Update code style * Update code style... — committed to kiku-jw/three.js by kiku-jw 5 years ago
Ah OK. The use of Group vs. Object3D is not relevant for memory disposal. Its a syntax clarity issue.
Is this issue particular to Angular in some way?
Also, can you turn your code example into a PR? It will make it easier to understand the intention. Moreover maybe we can come up with a more generalized solution that can be applied to other articulated rigs in threejs such as characters, camera controls, game objects etc…
You are also welcome to change Object3D to Group if you prefer it like that. If you do, please send it as a separate PR.