Skip to content

@web-engine-dev/ecs

A high-performance, archetype-based Entity Component System (ECS) library for TypeScript/JavaScript. Built for game engines and simulations with support for parallel execution, SharedArrayBuffer, worker threads, and advanced query systems.

Layer 2 · ECS Foundation

Features

  • Archetype Storage: Cache-friendly SoA (Structure of Arrays) layout — entities with the same component set share contiguous memory
  • Type-Safe Components: Define components with typed schemas backed by TypedArrays
  • Advanced Queries: Fluent query builder with with, without, withOptional, and access modes (read/write)
  • Deferred Commands: Safe entity mutations during system execution (applied between system runs)
  • Double-Buffered Events: Frame-synchronized event system (write this frame, read next frame)
  • Observer Pattern: React to component add/remove lifecycle changes
  • Parallel Execution: Multi-threaded support via SharedArrayBuffer + Web Workers
  • Entity IDs: 32-bit packed format — 20-bit index + 12-bit generation (4M live entities, recycle detection)
  • Sparse Components: O(1) add/remove for frequently toggled components (e.g., cooldowns, status effects)

Installation

bash
npm install @web-engine-dev/ecs
# or
pnpm add @web-engine-dev/ecs

Quick Start

typescript
import { World, defineComponent, defineTag, queryBuilder } from '@web-engine-dev/ecs';

// Define components
const Position = defineComponent('Position', { x: 'f32', y: 'f32', z: 'f32' });
const Velocity = defineComponent('Velocity', { x: 'f32', y: 'f32', z: 'f32' });
const IsPlayer = defineTag('IsPlayer');

// Create world
const world = new World();

// Spawn entities
const player = world
  .spawn()
  .with(Position, { x: 0, y: 0, z: 0 })
  .with(Velocity, { x: 1, y: 0, z: 0 })
  .with(IsPlayer)
  .id();

// Query entities
const query = queryBuilder().with(Position, Velocity).read(Velocity).write(Position).build();

// System execution
for (const archetype of query.archetypes) {
  const positions = archetype.getColumn(Position);
  const velocities = archetype.getColumn(Velocity);

  for (let i = 0; i < archetype.count; i++) {
    positions[i].x += velocities[i].x * dt;
    positions[i].y += velocities[i].y * dt;
  }
}

Core Concepts

Components

typescript
// Data component with typed schema
const Position = defineComponent('Position', {
  x: 'f32',
  y: 'f32',
  z: 'f32',
});

// Tag (zero-size marker)
const IsPlayer = defineTag('IsPlayer');

// Sparse component (O(1) add/remove)
const Cooldown = defineComponent('Cooldown', { remaining: 'f32' }, { sparse: true });

Queries

typescript
const query = queryBuilder()
  .with(Position, Velocity) // Required components
  .without(Frozen) // Excluded components
  .withOptional(Acceleration) // Optional components
  .read(Velocity) // Read-only access
  .write(Position) // Write access
  .build();

Commands (Deferred Mutations)

typescript
commands.spawn().with(Position, { x: 0, y: 0, z: 0 });
commands.insert(entity, Health, { value: 100 });
commands.remove(entity, Poisoned);
commands.despawn(entity);

Events

typescript
// Write events
const writer = world.eventWriter<DamageEvent>('damage');
writer.send({ target: entity, amount: 10 });

// Read events (double-buffered)
const reader = world.eventReader<DamageEvent>('damage');
for (const event of reader.read()) {
  // Process damage
}

Resources

typescript
world.insertResource(GameTime, { delta: 0, elapsed: 0 });
const time = world.getResource(GameTime);

Schema Types

TypeDescription
i8Signed 8-bit integer
i16Signed 16-bit
i32Signed 32-bit
u8Unsigned 8-bit
u16Unsigned 16-bit
u32Unsigned 32-bit
f3232-bit float
f6464-bit float
boolBoolean

Proprietary software. All rights reserved.