System
Systems define entity behavior by operating on components.
Overview
In bitECS, systems are just functions. There's no formal System class or registration - you simply write functions that query for entities and update their component data.
Just Functions
Data-Oriented
Composable
const movementSystem = (world) => {
for (const eid of query(world, [Position, Velocity])) {
Position.x[eid] += Velocity.x[eid]
Position.y[eid] += Velocity.y[eid]
}
}
// Call the system
movementSystem(world)Defining Systems
A system is a function that takes a world and operates on entities:
import { query } from 'bitecs'
// Movement system - updates positions based on velocity
const movementSystem = (world) => {
for (const eid of query(world, [Position, Velocity])) {
Position.x[eid] += Velocity.x[eid]
Position.y[eid] += Velocity.y[eid]
}
}
// Gravity system - applies gravity to entities with mass
const gravitySystem = (world) => {
const GRAVITY = 9.81
for (const eid of query(world, [Velocity, Mass])) {
Velocity.y[eid] += GRAVITY * Mass.value[eid]
}
}
// Collision system - handles entity collisions
const collisionSystem = (world) => {
const entities = query(world, [Position, Collider])
for (let i = 0; i < entities.length; i++) {
for (let j = i + 1; j < entities.length; j++) {
const a = entities[i]
const b = entities[j]
// Check collision between a and b...
}
}
}Common System Patterns
| Pattern | Description |
|---|---|
| Transform | Update position, rotation, scale based on velocity |
| Input | Read keyboard/mouse state, update Input component |
| AI | Make decisions, set target positions or velocities |
| Physics | Apply forces, handle collisions, resolve constraints |
| Animation | Update sprite frames, blend between states |
| Render | Draw entities to canvas/WebGL |
| Cleanup | Remove dead entities, process removals |
System Pipeline
Organize systems into a pipeline that runs each frame:
// Define your system pipeline
const systems = [
timeSystem, // Update delta time first
inputSystem, // Read player input
aiSystem, // AI decisions
movementSystem, // Apply velocities
gravitySystem, // Apply gravity
collisionSystem, // Handle collisions
cleanupSystem, // Remove dead entities
renderSystem, // Draw everything
]
// Run all systems
const runSystems = (world) => {
for (const system of systems) {
system(world)
}
}
// Game loop
const loop = () => {
runSystems(world)
requestAnimationFrame(loop)
}
loop()Pipeline Helper
Create a reusable pipeline function to compose systems:
const pipeline = (...systems) => (world) => {
for (const system of systems) system(world)
}
const update = pipeline(
timeSystem,
inputSystem,
movementSystem,
renderSystem
)
// Run all systems
update(world)- Input → AI → Movement (input affects AI affects movement)
- Movement → Collision (move first, then resolve overlaps)
- Everything → Render (draw after all updates complete)
- Cleanup at end (remove entities after they're processed)
Conditional Systems
Run systems only when certain conditions are met:
// Only run when game is not paused
const gameplayPipeline = (world) => {
if (world.paused) return
movementSystem(world)
collisionSystem(world)
aiSystem(world)
}
// Only run every N frames
let frameCount = 0
const throttledAI = (world) => {
frameCount++
if (frameCount % 3 !== 0) return // Run every 3rd frame
for (const eid of query(world, [AI])) {
// Expensive AI calculations...
}
}
// Phase-based systems
const renderPhase = (world) => {
renderBackground(world)
renderEntities(world)
renderUI(world)
renderDebug(world)
}Delta Time
Use custom world context to pass delta time to systems:
// World with time context
const world = createWorld({
time: {
delta: 0,
elapsed: 0,
then: performance.now(),
}
})
// Time system - updates delta and elapsed time
const timeSystem = (world) => {
const now = performance.now()
world.time.delta = (now - world.time.then) / 1000 // seconds
world.time.elapsed += world.time.delta
world.time.then = now
}
// Use delta time in movement (frame-rate independent)
const movementSystem = (world) => {
const dt = world.time.delta
for (const eid of query(world, [Position, Velocity])) {
Position.x[eid] += Velocity.x[eid] * dt
Position.y[eid] += Velocity.y[eid] * dt
}
}Game Loop Integration
For browser games, use requestAnimationFrame. For server-side or headless simulations, Bun.sleep provides sub-millisecond precision:
Browser (requestAnimationFrame)
const gameLoop = () => {
timeSystem(world)
inputSystem(world)
movementSystem(world)
collisionSystem(world)
renderSystem(world)
requestAnimationFrame(gameLoop)
}
gameLoop()Server / Headless (Bun.sleep)
Bun.sleep is much faster and more precise than Node's setTimeout, enabling sub-millisecond timing for high-frequency game loops:
const TICK_RATE = 60
const TICK_MS = 1000 / TICK_RATE
const gameLoop = async () => {
while (true) {
const start = performance.now()
timeSystem(world)
physicsSystem(world)
aiSystem(world)
networkSyncSystem(world)
const elapsed = performance.now() - start
const sleepTime = Math.max(0, TICK_MS - elapsed)
await Bun.sleep(sleepTime)
}
}
gameLoop()Fixed Timestep
For deterministic physics, use a fixed timestep with accumulator:
const FIXED_STEP = 1 / 60 // 60 updates per second
let accumulator = 0
const fixedGameLoop = () => {
const now = performance.now()
const frameTime = (now - world.time.then) / 1000
world.time.then = now
accumulator += frameTime
// Run physics at fixed rate (may run multiple times per frame)
while (accumulator >= FIXED_STEP) {
physicsSystem(world, FIXED_STEP)
accumulator -= FIXED_STEP
}
// Render with interpolation for smoothness
const alpha = accumulator / FIXED_STEP
renderSystem(world, alpha)
requestAnimationFrame(fixedGameLoop)
}Best Practices
Single Responsibility
Avoid State
Use isNested
isNested to prevent iterator invalidation.Profile First
// Nested queries need isNested to prevent iterator invalidation
const collisionSystem = (world) => {
for (const a of query(world, [Position, Collider])) {
for (const b of query(world, [Position, Collider], isNested)) {
// ✓ Safe - isNested prevents removal commits during iteration
}
}
}
// Keep systems focused
const inputSystem = (world) => { /* Only input */ }
const aiSystem = (world) => { /* Only AI */ }
const movementSystem = (world) => { /* Only movement */ }
// Avoid god systems that do everything
const updateEverything = (world) => {
// ❌ 500 lines of mixed concerns
}