first commit
This commit is contained in:
108
docs/gaming/games/ecs/src/world/world.md
Normal file
108
docs/gaming/games/ecs/src/world/world.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# World
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`World` is the central container for an ECS instance. It owns the component registry, entity pool, archetype graph, query cache, system scheduler, and the typed `EventBus` that fires entity-lifecycle events. Every public mutator is instrumented with OpenTelemetry — `spawn` / `despawn` increment a counter, `execute` records into a histogram — so production behaviour is observable through the same in-memory `MeterProvider` used in tests.
|
||||
|
||||
The `createWorld(config?)` factory is the canonical entry point. `World` itself is exported so consumers can subclass or interrogate the instance.
|
||||
|
||||
## Features
|
||||
|
||||
- Archetype-graph mutations: `addComponent` / `removeComponent` move entities between archetypes lazily.
|
||||
- Idempotent `addComponent` — no-op when the component already exists.
|
||||
- Component validation via descriptor-attached Zod schemas; throws `ECS.COMPONENT_VALIDATION_FAILED` on rejection.
|
||||
- System scheduling with phase + priority ordering through `SystemScheduler`.
|
||||
- `snapshot` / `restore` round-trip with Zod-validated payloads.
|
||||
- Typed `EventBus<WorldEventMap>` — single-object payloads matching the `@suaveplan/events` shape.
|
||||
- OpenTelemetry counter (`ecs.entity.lifecycle_total`) and histogram (`ecs.world.execute_duration_ms`).
|
||||
|
||||
## 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 p = world.getComponent(entity, Position)!;
|
||||
const v = world.getComponent(entity, Velocity)!;
|
||||
world.setComponent(entity, Position, {
|
||||
x: p.x + v.dx * dt,
|
||||
y: p.y + v.dy * dt,
|
||||
});
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const player = world.spawn();
|
||||
world.addComponent(player, Position, { x: 0, y: 0 });
|
||||
world.addComponent(player, Velocity, { dx: 5, dy: 0 });
|
||||
world.execute(1 / 60);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { createWorld, defineComponent } from "@suaveplan/ecs";
|
||||
|
||||
const Position = defineComponent("Position", { x: 0, y: 0 });
|
||||
const world = createWorld({ maxComponents: 128 });
|
||||
world.registerComponent(Position);
|
||||
|
||||
world.events.on("entitySpawned", ({ entity }) => {
|
||||
console.log("spawned", entity);
|
||||
});
|
||||
world.events.on("componentAdded", ({ entity, component }) => {
|
||||
console.log(component, "->", entity);
|
||||
});
|
||||
|
||||
const snapshot = world.snapshot();
|
||||
// ... ship snapshot over the wire ...
|
||||
|
||||
const replay = createWorld();
|
||||
replay.registerComponent(Position);
|
||||
replay.restore(snapshot);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Member | Description |
|
||||
|---|---|
|
||||
| `createWorld(config?)` / `new World(config)` | Build a world. `maxComponents` defaults to 256. |
|
||||
| `events` | `EventBus<WorldEventMap>` for entity lifecycle. |
|
||||
| `registry` | Underlying `ComponentRegistry`. |
|
||||
| `registerComponent(schema)` | Pre-register a schema (idempotent). |
|
||||
| `spawn()` | Allocate an entity and emit `entitySpawned`. |
|
||||
| `despawn(entity)` | Despawn and recycle the entity. No-op when unknown. |
|
||||
| `isAlive(entity)` | Boolean liveness check. |
|
||||
| `addComponent(entity, schema, data?)` | Attach a component; validates Zod-attached schemas. |
|
||||
| `removeComponent(entity, schema)` | Detach a component. |
|
||||
| `getComponent(entity, schema)` | Read a component (or `undefined`). |
|
||||
| `setComponent(entity, schema, data)` | Merge partial data; revalidates. |
|
||||
| `hasComponent(entity, schema)` | Membership check. |
|
||||
| `query(descriptor)` | One-off entity query. |
|
||||
| `addSystem(system)` / `removeSystem(name)` | System lifecycle. |
|
||||
| `execute(dt)` | Run every enabled system; records duration histogram. |
|
||||
| `snapshot()` / `restore(snap)` | Persistence pair. `restore` Zod-validates the input. |
|
||||
| `clear()` | Wipe entities + archetypes, keep systems. |
|
||||
| `entityCount` / `archetypeCount` / `getSystems()` | Inspection accessors. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Archetype identity is the bitset key (`mask.toKey()`); `getOrCreateArchetype` deduplicates archetypes through a `Map<string, Archetype>`.
|
||||
- `addComponent` and `removeComponent` go through a swap-remove from the old archetype, mutate the component map, then insert into the new archetype. The destination archetype is materialised lazily.
|
||||
- `setComponent` does **not** change archetype membership — it merges into the existing column slot and re-validates against the descriptor's Zod schema.
|
||||
- `restore` is the only persistence boundary that validates input with Zod (`WorldSnapshotSchema`). Failures throw `ECS.SNAPSHOT_INVALID` with the Zod issues in `context`. Unknown component names in the snapshot are silently skipped, so adding new component types to consumer code does not break older snapshots.
|
||||
- OTel instruments resolve through the global `MeterProvider`. In tests, the package's `tests/setup/polyfills.ts` registers an in-memory `MeterProvider` before any module imports so the instruments bind to it.
|
||||
- The world's `EventBus` uses single-object payloads (`{ entity }`, `{ entity, component }`) consistent with the `@suaveplan/events` contract, rather than upstream's tuple shape.
|
||||
Reference in New Issue
Block a user