ObjectManagementSystem
Core entity lifecycle system. Manages the entity creation queue, the scene graph parent-child hierarchy, and the registries for entity and component factories.
Overview
ObjectManagementSystem runs at the start of each frame (before all other systems) and is responsible for:
- Flushing the entity creation queue — calling the registered
EntityFactoryfor each queuedcreaterequest and invoking the optionalonCreatedcallback with the new entity ID. - Reconciling parent-child relationships in the THREE.js scene graph each frame by reading
ParentIdcomponents and reparentingObject3DRefobjects when the hierarchy changes. - Maintaining the entity factory registry (
register) and component factory registry (registerComponentFactory) used by higher-level spawn helpers.
Entity factory registry
Object kinds (e.g. "model", "cube", "image") are registered with register(kind, factory). When world.create(kind, payload) is called, the system enqueues the request and the factory is invoked on the next frame.
Component factory registry
Component kinds (e.g. "animation", "physics") are registered with registerComponentFactory(kind, factory). These are called by save/hydration flows to restore component state onto existing entities.
Queried components
| Component | Access |
|---|---|
ParentId | Read (desired parent entity ID) |
Object3DRef | Read + mutate (scene graph reparenting) |
TransformComponent | Read (applied to object after reparenting) |
Behavior notes
Ordering — this system must run first in the world's update loop. All other systems assume entities and parent relationships are already resolved when they run.
Parent reconciliation — if a
ParentIdrefers to an entity that does not yet have anObject3DRef(e.g. the parent is still loading), reparenting is deferred to the next frame.Scene root — entities without a
ParentId(or withParentId.parentId === null) are kept as direct children of the THREE.js scene root.Audio listener —
getOrCreateAudioListener()lazily creates a singleTHREE.AudioListeneron the world camera and is used by audio object factories.Entity deletion —
deleteEntity(world, entityId)removes the entity from the ECS world and itsObject3Dfrom the THREE.js scene graph. Passdispose = trueas the third argument to also call.dispose()on allBufferGeometryandMaterialinstances in the subtree, freeing GPU memory. Use this for dynamically spawned objects that will not be reused. Avoid it when geometry or materials are shared with other entities.ts// Remove from scene only (geometry/materials are kept on the GPU — safe if shared) objects.deleteEntity(world, entityId); // Remove from scene and free GPU memory objects.deleteEntity(world, entityId, true);
API reference
requestCreate(kind, payload?, onCreated?)
Queue a new entity creation request. The factory for the given kind runs on the next frame; the optional onCreated callback fires with the new entityId:
const oms = this.world.getObjectManagementSystem();
oms?.requestCreate("cube", {
name: "DynamicCube",
transform: { position: { x: 0, y: 2, z: 0 } },
}, (entityId) => {
// entityId is ready — add more components here
});createComponentNow(kind, world, entityId, ...args)
Synchronously call the registered component factory for kind on an existing entity. Use this instead of world.addComponent when the component needs runtime setup (Rapier bodies, Web Audio nodes, Three.js material instances):
// Add physics with full Rapier body + collider setup
oms.createComponentNow("physics", world, entityId, {
rigidbody: { type: "dynamic", mass: 1, gravityScale: 1 },
collider: { shape: "box" },
});
// Add a material with a Three.js material instance
oms.createComponentNow("material", world, entityId, { color: "#ff0000" });Registered factory kinds: "animation", "audio", "camera", "physics", "environment", "entityType", "image", "imageTarget", "imageTargetAnchor", "light", "material", "mesh", "meshGeometry", "model", "name", "object3DRef", "parentId", "postprocessing", "script", "sprite", "transform", "video", "instancedMesh".
deleteEntity(world, entityId, dispose?)
Remove an entity from the ECS world and its Object3D from the Three.js scene graph. Pass dispose = true to also free GPU memory by calling .dispose() on all BufferGeometry and Material instances in the subtree:
// Remove from scene, keep GPU resources (safe if geometry is shared)
oms.deleteEntity(world, entityId);
// Remove from scene and free GPU memory (use for dynamically spawned objects)
oms.deleteEntity(world, entityId, true);setParent(world, entityId, parentId, options?)
Re-parent an entity at runtime. Updates the ParentId component and reconciles the Three.js scene graph on the same frame. By default (preserveWorldTransform: true), world-space position and rotation are preserved by adjusting the local transform:
// Attach child to a parent entity (preserves world position)
oms.setParent(world, childEntityId, parentEntityId);
// Detach from parent — make direct child of scene root
oms.setParent(world, childEntityId, null);
// Reparent without preserving world position (local transform reset)
oms.setParent(world, childEntityId, parentEntityId, { preserveWorldTransform: false });setParent guards against cycles and returns false if the operation would create a loop or if either entity does not exist.

