Files
skills/docs/gaming/games/ecs/src/component-registry/component-registry.md
2026-05-29 08:20:56 +01:00

3.6 KiB

ComponentRegistry

(no submodules)

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

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

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.