Skip to content

Commit 4bef46e

Browse files
felixtrzmeta-codesync[bot]
authored andcommitted
feat(core): add entity.dispose() for GPU resource cleanup on destroy
Summary: - Add entity.dispose() method that destroys entity and frees GPU resources (geometry, materials, textures) - Existing entity.destroy() behavior unchanged - only removes from scene graph, preserves GPU resources - Add disposeObject3DResources() to World that traverses Object3D hierarchy and calls dispose on all resources - Use internal _disposeResources flag to signal disposal intent from entity to world - Document both methods in docs/concepts/ecs/entity.md with usage examples and warning about shared resources - Update docs/concepts/ecs/lifecycle.md to describe both cleanup methods Reviewed By: zjm-meta Differential Revision: D88606732 Privacy Context Container: L1334777 fbshipit-source-id: 4ba5d91673ee76a8b5af72a512f318701b858283
1 parent ff62ead commit 4bef46e

File tree

4 files changed

+141
-4
lines changed

4 files changed

+141
-4
lines changed

docs/concepts/ecs/entity.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,65 @@ e.setValue(Health, 'current', 75);
5252
const pos = e.getVectorView(Transform, 'position'); // Float32Array
5353
pos[1] += 1; // move up
5454
```
55+
56+
## Destroying Entities
57+
58+
IWSDK provides two ways to destroy entities:
59+
60+
### `entity.destroy()`
61+
62+
Removes the entity from the ECS and detaches its `object3D` from the scene graph:
63+
64+
```ts
65+
entity.destroy();
66+
```
67+
68+
This is safe and non-destructive to GPU resources. The geometry, materials, and textures remain in GPU memory and can be reused by other entities.
69+
70+
### `entity.dispose()`
71+
72+
Destroys the entity AND disposes all GPU resources (geometry, materials, textures):
73+
74+
```ts
75+
entity.dispose();
76+
```
77+
78+
::: warning
79+
**Use with caution!** `dispose()` is destructive and will free GPU memory for:
80+
- All geometries attached to the entity's `object3D` and its children
81+
- All materials (including shared materials)
82+
- All textures referenced by those materials
83+
84+
If other entities share the same geometry, material, or texture, they will break after calling `dispose()`.
85+
:::
86+
87+
### When to use each
88+
89+
| Scenario | Method |
90+
|----------|--------|
91+
| Removing an entity that uses shared/reusable assets | `destroy()` |
92+
| Removing an entity with unique, one-off geometry/materials | `dispose()` |
93+
| Level unloading with asset reuse | `destroy()` |
94+
| Full cleanup of procedurally generated content | `dispose()` |
95+
96+
### Example: Safe cleanup pattern
97+
98+
```ts
99+
// If you created unique resources, dispose them
100+
const geometry = new BoxGeometry(1, 1, 1);
101+
const material = new MeshStandardMaterial({ color: 0xff0000 });
102+
const mesh = new Mesh(geometry, material);
103+
const entity = world.createTransformEntity(mesh);
104+
105+
// Later: full cleanup since resources aren't shared
106+
entity.dispose();
107+
```
108+
109+
```ts
110+
// If using loaded/shared assets, just destroy
111+
const gltf = await AssetManager.loadGLTF('model.glb');
112+
const entity = world.createTransformEntity(gltf.scene.clone());
113+
114+
// Later: don't dispose - other entities may use the same materials
115+
entity.destroy();
116+
```

docs/concepts/ecs/lifecycle.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,12 @@ this.queries.panels.subscribe('disqualify', (e) => {
6262

6363
- `createEntity()` creates a bare entity (no `object3D`).
6464
- `createTransformEntity(object?, parentOrOptions?)` creates an entity with an `object3D`, injects a `Transform`, and parents it under the level root or scene based on options.
65-
- `entity.destroy()` marks the entity inactive, clears its bitmask, resets query membership, and detaches its `object3D` from the scene.
65+
- `entity.destroy()` marks the entity inactive, clears its bitmask, resets query membership, and detaches its `object3D` from the scene. GPU resources (geometry, materials, textures) are preserved.
66+
- `entity.dispose()` does everything `destroy()` does, plus disposes all GPU resources. Use with caution when resources may be shared.
67+
68+
::: tip
69+
Use `destroy()` by default. Only use `dispose()` when you're certain the entity's resources aren't shared with other entities.
70+
:::
6671

6772
Parenting rules
6873

packages/core/src/ecs/entity.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,37 @@
66
*/
77

88
import type { PointerEventsMap } from '@pmndrs/pointer-events';
9+
import { Entity as ElicsEntity } from 'elics';
910
import type { Object3D, Object3DEventMap } from '../runtime/index.js';
1011

1112
declare module 'elics' {
1213
interface Entity {
1314
object3D?: Object3D<Object3DEventMap & PointerEventsMap>;
15+
/** @internal Flag to indicate GPU resources should be disposed on destroy */
16+
_disposeResources?: boolean;
17+
/**
18+
* Destroy the entity and dispose of its GPU resources (geometry, materials, textures).
19+
*
20+
* @remarks
21+
* Use this instead of `destroy()` when you want to fully clean up GPU memory.
22+
* Use with caution when resources may be shared across multiple entities.
23+
*
24+
* @example
25+
* ```ts
26+
* // Remove entity and free GPU resources
27+
* entity.dispose();
28+
* ```
29+
*/
30+
dispose(): void;
1431
}
1532
}
1633

34+
// Add dispose method to Entity prototype
35+
ElicsEntity.prototype.dispose = function (this: ElicsEntity) {
36+
(this as any)._disposeResources = true;
37+
this.destroy();
38+
};
39+
1740
export { Entity } from 'elics';
18-
/** Sentinel value used for no parent in Transform.parent. @category ECS */
41+
/** Sentinel value used for "no parent" in Transform.parent. @category ECS */
1942
export const NullEntity = -1;

packages/core/src/ecs/world.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { LevelTag } from '../level/index.js';
2121
import type { Object3DEventMap } from '../runtime/index.js';
2222
import {
23+
Material,
2324
Object3D,
2425
PerspectiveCamera,
2526
Scene,
@@ -88,11 +89,57 @@ export class World extends ElicsWorld {
8889
);
8990
this.entityManager.releaseEntityInstance = (entity: Entity) => {
9091
originalReleaseFunc(entity);
91-
entity.object3D?.removeFromParent();
92-
delete entity.object3D;
92+
const obj = entity.object3D;
93+
if (obj) {
94+
// Check if entity was marked for resource disposal
95+
if ((entity as any)._disposeResources) {
96+
this.disposeObject3DResources(obj);
97+
delete (entity as any)._disposeResources;
98+
}
99+
obj.removeFromParent();
100+
delete entity.object3D;
101+
}
93102
};
94103
}
95104

105+
/**
106+
* Dispose of an Object3D's GPU resources (geometry, materials, textures).
107+
* Traverses all descendants and cleans up disposable resources.
108+
*
109+
* @remarks
110+
* This is called automatically when an entity is destroyed with `disposeResources: true`.
111+
* Use with caution when resources may be shared across multiple entities.
112+
*/
113+
private disposeObject3DResources(object: Object3D): void {
114+
object.traverse((child: any) => {
115+
// Dispose geometry
116+
if (child.geometry) {
117+
child.geometry.dispose();
118+
}
119+
120+
// Dispose materials (can be single or array)
121+
if (child.material) {
122+
const materials: Material[] = Array.isArray(child.material)
123+
? child.material
124+
: [child.material];
125+
126+
for (const material of materials) {
127+
// Dispose textures attached to the material
128+
for (const key of Object.keys(material)) {
129+
const value = (material as any)[key];
130+
if (value && typeof value.dispose === 'function') {
131+
// Check if it's a texture (has isTexture property)
132+
if (value.isTexture) {
133+
value.dispose();
134+
}
135+
}
136+
}
137+
material.dispose();
138+
}
139+
}
140+
});
141+
}
142+
96143
createEntity(): Entity {
97144
return super.createEntity() as Entity;
98145
}

0 commit comments

Comments
 (0)