first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

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