# 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 `SystemDefinition`s 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 ```ts 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 ```ts 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.