Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View 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.