Web Engine Docs
Preparing documentation
Use the search bar to quickly find any topic
Preparing documentation
Use the search bar to quickly find any topic
Understanding the Entity-Component-System pattern that powers Web Engine. Learn why ECS enables high-performance game development.
Web Engine is built on the Entity-Component-System (ECS) architecture, a data-oriented design pattern that separates data (Components) from logic (Systems), enabling optimal performance and flexibility.
Traditional object-oriented game architectures often suffer from:
ECS solves these problems by treating game objects as simple IDs (Entities), storing data in flat, typed arrays (Components), and processing them in batches (Systems).
Simple integer IDs that represent game objects. No data, no behavior — just a unique identifier.
Pure data containers stored in TypedArrays. Position, velocity, health — raw values only.
Functions that process entities with specific component sets. All game logic lives here.
In Web Engine, entities are just numbers (unsigned 32-bit integers). They have no inherent meaning — they're simply indices into component arrays.
// Create a new entityconst eid = addEntity(world);// eid is just a number, e.g., 42console.log(eid); // 42// Remove an entityremoveEntity(world, eid);
Entity Recycling
When entities are removed, their IDs are recycled. This prevents ID bloat and keeps arrays compact. Web Engine uses bitECS under the hood, which handles this automatically.
Components are pure data — no methods, no logic. They're defined as schemas and stored in Structure of Arrays (SoA) format for cache efficiency. Web Engine uses bitECS, which stores components as TypedArrays for maximum performance.
import { defineComponent, Types } from "bitecs";// Define a Transform component with nested arraysexport const Transform = defineComponent({position: [Types.f32, 3], // [x, y, z]rotation: [Types.f32, 3], // Euler anglesscale: [Types.f32, 3], // [x, y, z]quaternion: [Types.f32, 4], // [x, y, z, w]parent: Types.eid, // Parent entity reference});// Add component to entityaddComponent(world, Transform, eid);// Access component data via TypedArraysTransform.position.x[eid] = 10.0;Transform.position.y[eid] = 0.0;Transform.position.z[eid] = -5.0;// Or using array notation for multi-value fieldsconst posArray = Transform.position;posArray[0][eid] = 10.0; // xposArray[1][eid] = 0.0; // yposArray[2][eid] = -5.0; // z
Notice how Transform.position.x is a TypedArray. Accessing Transform.position.x[eid] reads the X position for entity eid. Web Engine includes dozens of components covering transform, physics, rendering, animation, audio, and gameplay.
// RigidBody component for physics simulationexport const RigidBody = defineComponent({velocity: [Types.f32, 3], // Linear velocityangularVelocity: [Types.f32, 3], // Angular velocitymass: Types.f32, // Mass in kgfriction: Types.f32, // Coefficient of frictionrestitution: Types.f32, // Bouncinesstype: Types.ui8, // 0=Dynamic, 1=Fixed, 2=Kinematicccd: Types.ui8, // Continuous collision detectionlockRotation: [Types.ui8, 3], // Lock X, Y, Z rotation});// Collider component for collision detectionexport const Collider = defineComponent({type: Types.ui8, // Shape: Box, Sphere, Capsule, etc.size: [Types.f32, 3], // Shape dimensionsoffset: [Types.f32, 3], // Local offsetisSensor: Types.ui8, // Is trigger volumecollisionGroup: Types.ui16, // Collision group bitmaskcollisionMask: Types.ui16, // Collision filter mask});
Zero-GC Rule
Never create objects in the hot loop! Use pre-allocated vectors and reuse them. The ECS pattern enables this by keeping all data in flat TypedArrays. Web Engine achieves zero garbage collection during gameplay by following this principle religiously.
Systems are functions that iterate over entities matching a specific query (set of components). They're where all game logic lives.
import { defineQuery, defineSystem } from "bitecs";// Define a query for entities with Transform and Velocityconst movingQuery = defineQuery([Transform, Velocity]);// Define the systemexport const MovementSystem = defineSystem((world) => {const dt = world.dt; // Delta time// Iterate all entities matching the queryconst entities = movingQuery(world);for (let i = 0; i < entities.length; i++) {const eid = entities[i];// Apply velocity to positionTransform.position.x[eid] += Velocity.x[eid] * dt;Transform.position.y[eid] += Velocity.y[eid] * dt;Transform.position.z[eid] += Velocity.z[eid] * dt;}return world;});
Systems run every frame as part of the game loop. Web Engine organizes them into phases:
ECS achieves exceptional performance through several mechanisms:
Components are stored in contiguous TypedArrays. When a system iterates over entities, it reads memory sequentially, maximizing CPU cache hits. This is critical for high-performance games targeting 60+ FPS.
Traditional OOP (Array of Structures):Player1 -> [pos, vel, health, ...] <- scattered in memoryPlayer2 -> [pos, vel, health, ...] <- cache miss likelyEnemy1 -> [pos, vel, health, ...] <- cache miss likelyECS (Structure of Arrays):Position.x: [p1.x, p2.x, e1.x, ...] <- contiguous, cache-friendlyPosition.y: [p1.y, p2.y, e1.y, ...] <- contiguousPosition.z: [p1.z, p2.z, e1.z, ...] <- contiguousVelocity.x: [p1.vx, p2.vx, e1.vx, ...] <- contiguous...Result: ~10-100x better cache hit rate in tight loops
Because data lives in pre-allocated TypedArrays, there's no object creation during gameplay. This eliminates GC pauses that cause stuttering. Web Engine can run for hours without triggering a single garbage collection cycle.
// BAD: Creates garbage every framefunction updateBad(eid: number) {const pos = { x: 0, y: 0, z: 0 }; // Allocates objectpos.x = Transform.position.x[eid];pos.y = Transform.position.y[eid];pos.z = Transform.position.z[eid];return pos; // More garbage}// GOOD: Zero allocationsfunction updateGood(eid: number) {const x = Transform.position.x[eid];const y = Transform.position.y[eid];const z = Transform.position.z[eid];// Work directly with primitives}
Systems can run in parallel if they don't modify the same components. The clear data boundaries make it easy to identify safe parallelization. Web Engine uses Web Workers for physics and culling systems.
Web Engine's ECS architecture enables:
Web Engine is built on bitECS, the fastest ECS library for JavaScript. bitECS was chosen for several reasons:
import {createWorld, // Create ECS worldaddEntity, // Add new entityremoveEntity, // Remove entityaddComponent, // Add component to entityremoveComponent, // Remove componenthasComponent, // Check if entity has componentdefineQuery, // Create entity queryenterQuery, // Query for entities that just enteredexitQuery, // Query for entities that just exiteddefineComponent, // Define component schemaTypes, // TypedArray types (f32, ui8, etc.)Not, // Query modifier: exclude component} from 'bitecs';