Lifecycle
Mental model: lifecycle is about when the ECS does what. Understand boot, system setup, per‑frame execution, and teardown.
World lifecycle and boot sequence
When you call World.create(container, options) IWSDK:
- Constructs a
Worldinstance and registers core components/systems:Transform,Visibility,TransformSystem,VisibilitySystem. - Creates Three.js objects:
Scene,PerspectiveCamera,WebGLRenderer; enables WebXR. - Wraps the Scene in an entity (
world.sceneEntity) and initializes anactiveLevelentity beneath it. - Loads the authored level environment. Dome, IBL, and light components are explicit scene data; IWSDK does not inject missing lighting.
- Creates
XRInputManager; wiresplayer(XROrigin) andinputinto the world. - Registers core feature systems (always‑on UI, Audio; optional Locomotion/Grabbing) with explicit priorities.
- Initializes
AssetManager. - Starts the render loop (
renderer.setAnimationLoop): each tick setsvisibilityState, runsworld.update(delta, time), invokes registeredworld.onXRFramecallbacks when an XR frame exists, then renders. - If
options.xr.offeris 'once' or 'always', IWSDK offers an XR session after init (and re‑offers on end if 'always'). Otherwise, callworld.launchXR()manually when the user presses your XR button. - Preloads assets (if provided) and requests an initial level load via
world.loadLevel(url?). Later scene swaps can use eitherworld.loadLevel(url)orworld.loadSceneDocument(document).
Implications
- Systems run on every animation frame in priority order before the scene is rendered.
- Default priorities (negative numbers run earlier): Locomotion (−5), Input (−4), Grabbing (−3). You can pass
{ priority: number }when registering a system. visibilityStateis updated each frame from the XR session (ornon-immersive).
Choosing a per-frame API
Use a System's update(delta, time) for ordinary animation and application logic. Systems run in browser and immersive modes, participate in priority ordering, and keep behavior inside the ECS lifecycle.
Use world.onXRFrame((frame, delta, time) => { ... }) only when code needs the live XRFrame, such as raw hit-test results, depth information, or viewer poses. These callbacks run only while an immersive XR frame exists, after systems and before rendering. Call the returned unsubscribe function during cleanup.
Do not create a second requestAnimationFrame or renderer.setAnimationLoop for application behavior; IWSDK's existing render loop drives both APIs.
System lifecycle (per class)
init()- Subscribe to query
qualify/disqualifyevents. - Set up configs (
this.config.foo.subscribe(...)). - Wire DOM/renderer listeners; enqueue cleanups into
this.cleanupFuncsif you create disposables.
- Subscribe to query
update(delta, time)- Iterate your query sets. Use
for (const e of this.queries.name.entities). - Use
.peek()when reading config signals inside tight loops to avoid unnecessary reactivity. - Avoid allocations and nested loops in hot paths.
- Iterate your query sets. Use
destroy()- Dispose resources and undo listeners. IWSDK calls all
cleanupFuncsfor you.
- Dispose resources and undo listeners. IWSDK calls all
Query membership and when it changes
- Adding/removing a component on an entity (
addComponent/removeComponent) triggers query re‑evaluation for component presence. - Changing a component value with
setValuetriggers re‑evaluation for queries whosewhere:predicates depend on that component. getVectorViewreturns a typed slice; mutating it does NOT trigger re‑evaluation on its own. If a query depends on that field, write back viasetValueor mirror a scalar used in predicates.- Subscribe to membership edges:
this.queries.panels.subscribe('qualify', (e) => {
/* attach once */
});
this.queries.panels.subscribe('disqualify', (e) => {
/* cleanup */
});Entity lifecycle
createEntity()creates a bare entity (noobject3D).createTransformEntity(object?, parentOrOptions?)creates an entity with anobject3D, injects aTransform, and parents it under the level root or scene based on options.entity.destroy()marks the entity inactive, clears its bitmask, resets query membership, and detaches itsobject3Dfrom the scene. GPU resources (geometry, materials, textures) are preserved.entity.dispose()does everythingdestroy()does, plus disposes all GPU resources. Use with caution when resources may be shared.
TIP
Use destroy() by default. Only use dispose() when you're certain the entity's resources aren't shared with other entities.
Parenting rules
- If you pass
{ parent: Entity }, the resultingobject3Dis added under the parent’sobject3D. { persistent: true }forces parenting under the scene (survives level swaps).- Otherwise, entities created during a level load are parented under the active level root; persistent utilities under the scene.
Frame order recap
set visibilityState → world.update(delta,time) → onXRFrame callbacks → renderWithin world.update, systems run in ascending priority (more negative first). Use lower (more negative) priorities for input/physics that must run before visuals.
Frame diagram (text)
requestAnimationFrame / XRAnimationFrame
└─ compute delta,time
├─ world.visibilityState ← session.visibilityState
├─ world.update(delta,time)
│ ├─ systems @ priority … -5, -4, -3, 0, 1 …
│ └─ queries keep sets up to date (qualify/disqualify emitted)
├─ world.onXRFrame callbacks (active XR sessions only)
└─ renderer.render(scene,camera)