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,77 @@
# ComponentRegistry
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`ComponentRegistry` translates `ComponentSchema` references into stable integer IDs. These IDs are used as bit positions in archetype bitsets, and registering a schema is the first step before it can be added to any entity. The registry enforces three contracts: identity-stable registration (same schema → same ID), name-uniqueness (no two schemas may share a `name`), and a hard cap on how many distinct types can be registered (`maxComponents`, default 256, matching the bitset's typical 8-word allocation).
The registry is owned by `World` but exposed publicly so editors, profilers, and serialisation layers can iterate every registered type.
## Features
- Idempotent `register` — passing the same schema reference returns the same ID.
- Throws structured `EcsError` with stable codes for every failure mode (`COMPONENT_NAME_CONFLICT`, `COMPONENT_LIMIT_REACHED`, `COMPONENT_NOT_REGISTERED`).
- Bi-directional lookup: schema → ID, ID → schema, name → schema.
- `maxComponents` cap configured at construction; validates positive integer.
- `count`, `has`, and `schemas()` accessors for inspection.
## Usage: Basic Example
```ts
import { ComponentRegistry, defineComponent } from "@suaveplan/ecs";
const Position = defineComponent("Position", { x: 0, y: 0 });
const registry = new ComponentRegistry();
const positionId = registry.register(Position);
console.log(positionId); // 0
console.log(registry.register(Position) === positionId); // true
```
## Usage: Advanced Example
```ts
import {
ComponentRegistry,
defineComponent,
ECS_CODES,
EcsError,
} from "@suaveplan/ecs";
const registry = new ComponentRegistry(4);
registry.register(defineComponent("A", {}));
registry.register(defineComponent("B", {}));
registry.register(defineComponent("C", {}));
registry.register(defineComponent("D", {}));
try {
registry.register(defineComponent("E", {}));
} catch (err) {
if (err instanceof EcsError && err.code === ECS_CODES.COMPONENT_LIMIT_REACHED) {
console.warn("raise maxComponents in WorldConfig");
}
}
```
## API Reference
| Member | Description |
|---|---|
| `new ComponentRegistry(maxComponents?)` | Default `maxComponents` is 256. Rejects non-positive integers. |
| `register(schema)` | Return stable ID. Throws `ECS.COMPONENT_NAME_CONFLICT` / `ECS.COMPONENT_LIMIT_REACHED`. |
| `getId(schema)` | Return ID, or throw `ECS.COMPONENT_NOT_REGISTERED`. |
| `getSchema(id)` / `getSchemaByName(name)` | Lookups; return `undefined` when missing. |
| `has(schema)` | Boolean membership. |
| `count` | Number of registered schemas. |
| `schemas()` | Read-only snapshot in registration order. |
| `maxComponents` | Hard cap configured at construction. |
## Implementation Notes
- Three maps back the registry — schema → id, id → schema, name → schema. The slight memory overhead pays off in O(1) lookups across all three directions.
- IDs are dense and monotonically increasing starting at `0`. They are intentionally not stable across processes: the same schema registered second in process A and first in process B yields different IDs. Persistence layers use `schema.name`, never the ID.
- Name uniqueness is enforced via the dedicated `nameToSchema` map; this catches the most common bug — two `defineComponent("Position", …)` calls in different modules — at registration time rather than at the first archetype-create.
- The `maxComponents` cap is also the bitset size for archetype masks. Exceeding it would mean an archetype's mask runs off the end of its `Uint32Array`, so the limit is hard, not advisory.