3.9 KiB
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: falselets systems be added but skipped onexecute— 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/getAllaccessors 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
getOrderedsorts lazily — thesortedflag 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.defineSystemreturns a plain object, not a frozen one — performance-sensitive call sites can mutate fields likeenabledto gate execution at runtime without rebuilding the definition. The world contract still treats it as immutable.- The
queryfield referencesComponentSchemaobjects, not IDs. The world resolves them through its registry onaddSystem, 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.