ECS Architecture in WebXR
Updated: Jul 22, 2026
Deep dive into how IWSDK ECS architecture enables scalable, performant WebXR applications.
ECS follows data-oriented design principles that are especially important for VR performance:
Memory layout: Why columnar storage matters
Columnar storage groups each component field across entities, which keeps related values together.
Traditional OOP stores objects like this:
PlayerObject { health: 100, position: [0,1,0], velocity: [1,0,0] }
EnemyObject { health: 50, position: [5,1,2], velocity: [-1,0,0] }
ECS stores the same data like this:
Health: [100, 50, 75, ...] // all health values together
Position: [0,1,0, 5,1,2, ...] // all positions together
Velocity: [1,0,0, -1,0,0, ...] // all velocities together
Why this matters in VR:
- Cache efficiency: When updating health, we only touch health data (no position/velocity).
- SIMD potential: Process multiple health values in parallel.
- Memory predictability: Keeps data access regular during frame updates.
Queries compare component masks instead of scanning object fields.
Queries use bit masks for O(1) component checks:
Entity 12: Health(✓) + Position(✓) + AI(✓) = bitmask: 00000111
Entity 37: Health(✓) + Position(✓) + Player(✓) = bitmask: 00001011
Query { required: [Health, Position] } = mask: 00000011
└─ Entity 12: (00000111 & 00000011) == 00000011 ✓ matches
└─ Entity 37: (00001011 & 00000011) == 00000011 ✓ matches
This makes “find all entities with Health and Position” fast even with thousands of entities.
VR applications must hit 72-90fps consistently. IWSDK ECS helps by:
System priority architecture
System priority defines update order.
Frame Budget (11ms for 90fps):
┌─ Input System (-4) │ 1ms │ Read controllers, hands
├─ Locomotion (-5) │ 2ms │ Update player movement
├─ Physics (-2) │ 3ms │ Collision detection
├─ Game Logic (0) │ 2ms │ Your gameplay systems
├─ UI System (1) │ 1ms │ Update spatial panels
└─ Render prep (2) │ 2ms │ Frustum culling, LOD
└────┘
11ms total budget
Systems with more negative priority run first, ensuring input lag stays minimal.
Query-driven optimization
Queries limit each system to matching entities.
Smart systems only process entities that need updates:
export class LODSystem extends createSystem({
// Only process visible objects that moved
needsLODUpdate: {
required: [Mesh, Transform, Visibility],
where: [eq(Visibility, 'changed', true)],
},
}) {
update() {
// Process only moved, visible objects
for (const entity of this.queries.needsLODUpdate.entities) {
// Update level-of-detail based on distance to camera
this.updateLOD(entity);
entity.setValue(Visibility, 'changed', false);
}
}
}
Composition Patterns for WebXR
Small components let systems compose behavior.
Build complex VR interactions through component composition:
// Make any object grabbable
entity.addComponent(Grabbable);
entity.addComponent(RigidBody); // Physics integration
// Make it also glowable
entity.addComponent(Interactable, { glowColor: [0, 1, 0] });
// Make it respond to voice commands
entity.addComponent(VoiceTarget, { keywords: ['pick up', 'grab'] });
Each system operates on its own. GrabSystem, GlowSystem, and VoiceSystem can work together without direct links.
Parent-child entities model nested objects.
WebXR often needs nested objects (hand → fingers → joints):
// Create hand hierarchy
const hand = world.createTransformEntity();
hand.addComponent(HandTracking);
const thumb = world.createTransformEntity(undefined, { parent: hand });
thumb.addComponent(FingerJoint, { type: 'thumb' });
const index = world.createTransformEntity(undefined, { parent: hand });
index.addComponent(FingerJoint, { type: 'index' });
Transform system automatically handles parent-child matrix updates.
Entity capacity depends on active components, query membership, system work, and the rest of the scene. Profile a representative scene on the target headset instead of relying on a fixed entity limit.
Use a defined query instead of checking every entity each frame.
// ❌ Inefficient: checks every entity every frame
for (const entity of world.entities) {
if (entity.hasComponent(Health) && entity.hasComponent(AI)) {
// process
}
}
// ✅ Efficient: precomputed set, direct iteration
for (const entity of this.queries.healthyAI.entities) {
// process only matching entities
}
Packed component storage and query indices consume more memory as entity and query counts grow. Keep component schemas focused, limit unnecessary query membership, and measure memory and frame time on the target headset.
Integration with Three.js and WebXR
IWSDK links ECS entities to Three.js objects.
IWSDK bridges ECS data and Three.js scene graph:
ECS Side: Three.js Side:
───────── ──────────────
Entity 12 ←──linked──→ Object3D
├─ Transform ├─ position: [1,2,3]
│ └─ position: [1,2,3] └─ quaternion: [...]
└─ Mesh └─ mesh: BoxGeometry
└─ geometry: "box"
Transform system automatically syncs ECS component data to Three.js matrices.
The world exposes WebXR session state to systems.
Session Start:
├─ World.visibilityState → 'visible'
├─ Input systems activate (hand/controller tracking)
├─ Locomotion systems start physics updates
└─ Render loop switches to XR frame timing (90fps)
Session End:
├─ World.visibilityState → 'non-immersive'
├─ Input systems pause expensive tracking
├─ `world.camera` auto-restored to its pre-XR local transform/projection
│ (one rAF after WebXRManager teardown; opt out with
│ `xr: { restoreCameraOnExit: false }`)
└─ Render loop returns to 60fps
Systems can check world.visibilityState to reduce CPU load when VR headset is removed.
Query counts show how much work a system can perform.
console.log('System performance:');
for (const [name, query] of Object.entries(this.queries)) {
console.log(` ${name}: ${query.entities.size} entities`);
}
Inspect component membership when profiling entity composition.
// Check component distribution
world.entityManager.entities.forEach((entity) => {
console.log(
`Entity ${entity.index}:`,
entity.getComponents().map((c) => c.id),
);
});
Enable built-in performance monitoring to inspect per-system frame time:
world.enablePerformanceMonitoring = true; // Shows system timing
This design supports both small demos and larger VR apps. Profile the app to keep frame time stable as the scene grows.
See
ECS concepts for related entity, component, query, and system guidance.