Initial Commit
This commit is contained in:
97
packages/gaming/games/ecs/src/system/system.md
Normal file
97
packages/gaming/games/ecs/src/system/system.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# System & SystemScheduler
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user