Files
skills/docs/gaming/games/ecs/src/system/system.md
2026-05-29 08:20:56 +01:00

3.9 KiB

System & SystemScheduler

(no submodules)

Purpose

A system is the unit of behaviour in an ECS — a function that runs over every entity matching a query and mutates component state. defineSystem is the factory that fills in sensible defaults (phase: "update", priority: 0, enabled: true) and produces a frozen SystemDefinition. SystemScheduler is the world's internal sorted registry: it orders systems by phase then priority, lazily resorting only when the set changes.

Phase ordering is fixed at preUpdate → update → postUpdate → render, mirroring the canonical game-loop pipeline. Priority breaks ties within a phase, lowest first.

Features

  • Frozen-by-default SystemDefinitions with sensible defaults.
  • enabled: false lets systems be added but skipped on execute — useful for runtime toggling.
  • Stable phase order: preUpdate < update < postUpdate < render.
  • Insertion order is the implicit tiebreaker when priority is equal (Array#sort is stable in modern engines).
  • has(name) membership check used by the world to enforce unique system names.
  • count / getAll accessors for inspection.

Usage: Basic Example

import { createWorld, defineComponent, defineSystem } from "@suaveplan/ecs";

const Position = defineComponent("Position", { x: 0, y: 0 });
const Velocity = defineComponent("Velocity", { dx: 1, dy: 0 });

const world = createWorld();
world.addSystem(defineSystem({
    name: "physics",
    query: { include: [Position, Velocity] },
    execute: ({ entities, world, dt }) => {
        for (const entity of entities) {
            const pos = world.getComponent(entity, Position)!;
            const vel = world.getComponent(entity, Velocity)!;
            world.setComponent(entity, Position, {
                x: pos.x + vel.dx * dt,
                y: pos.y + vel.dy * dt,
            });
        }
    },
}));

world.execute(1 / 60);

Usage: Advanced Example

import { defineSystem, SystemScheduler } from "@suaveplan/ecs";

const scheduler = new SystemScheduler();
scheduler.add(defineSystem({
    name: "input",
    phase: "preUpdate",
    priority: 0,
    query: { include: [] },
    execute: () => undefined,
}));
scheduler.add(defineSystem({
    name: "physics",
    priority: 10,
    query: { include: [] },
    execute: () => undefined,
}));
scheduler.add(defineSystem({
    name: "gameplay",
    priority: 5,
    query: { include: [] },
    execute: () => undefined,
}));

for (const sys of scheduler.getOrdered()) {
    // order: input, gameplay, physics
}

API Reference

Member Description
defineSystem(options) Build a SystemDefinition with phase / priority / enabled defaults.
SystemScheduler#add(system) Register a system. Marks scheduler dirty.
SystemScheduler#remove(name) Remove by name. Returns false for unknowns.
SystemScheduler#has(name) Membership check.
SystemScheduler#getOrdered() Read-only execution-ordered snapshot.
SystemScheduler#getAll() Read-only unordered snapshot.
SystemScheduler#count Number of registered systems.

Implementation Notes

  • getOrdered sorts lazily — the sorted flag clears on add/remove and is re-set after the first sort. The world is therefore free to call it once per tick without paying repeated sort cost when no mutation occurred.
  • defineSystem returns a plain object, not a frozen one — performance-sensitive call sites can mutate fields like enabled to gate execution at runtime without rebuilding the definition. The world contract still treats it as immutable.
  • The query field references ComponentSchema objects, not IDs. The world resolves them through its registry on addSystem, registering any not-yet-known schemas at that point.
  • The phase order is encoded as a numeric lookup table (PHASE_ORDER), which keeps the sort key access branch-free.