# Entity Component System (ECS) Source: `src/dusk/entity/` ## Core concepts - **Entity** (`entityid_t` = `uint8_t`) -- a numeric ID. No data of its own; just an index into the entity manager pool. - **Component** -- a plain data struct registered in `componentlist.h`. Stores state; no behaviour. - **System** -- functions that query all entities with a given component type and act on them each tick. ## Hard limits | Constant | Value | |----------|-------| | `ENTITY_COUNT_MAX` | 64 | | `ENTITY_COMPONENT_COUNT_MAX` | 16 per entity | | Total component slots | 1024 (64 x 16) | | Update callbacks per entity | 5 | | Dispose callbacks per entity | 5 | `ENTITY_ID_INVALID = 0xFF`, `COMPONENT_ID_INVALID = 0xFF`. ## Global state ```c extern entitymanager_t ENTITY_MANAGER; // .entities[64] -- entity structs // .components[1024] -- all component data (entity * 16 + comp) // .entitiesWithComponent -- O(1) lookup indexed by [type * 64 + entityId] ``` ## Entity lifecycle ```c entityid_t id = entityManagerAdd(); // reserve first inactive slot entityInit(id); // zero the entity, mark active componentid_t posId = entityAddComponent(id, COMPONENT_TYPE_POSITION); componentid_t rendId = entityAddComponent(id, COMPONENT_TYPE_RENDERABLE); // Per-frame update (called by entityManagerUpdate): entityUpdate(id); // Cleanup: entityDispose(id); // dispose components, mark inactive entityDisposeDeep(id); // dispose self + entire position hierarchy ``` ## Component registration (X-macro) All component types are declared in a single table in `src/dusk/entity/componentlist.h`: ```c X(NAME, type_t, fieldName, initFn, disposeFn, renderFn) ``` This generates: - `COMPONENT_TYPE_NAME` enum value - Union field `fieldName` in `componentdata_t` - Entry in `COMPONENT_DEFINITIONS[]` with `init` / `dispose` / `render` function pointers (any may be `NULL`) Current registered components: | Enum suffix | Struct | Notes | |-------------|--------|-------| | `POSITION` | `entityposition_t` | Transform + parent/child hierarchy | | `CAMERA` | `entitycamera_t` | View matrix setup | | `RENDERABLE` | `entityrenderable_t` | Sprite batch, shader material, or custom draw | | `PHYSICS` | `entityphysics_t` | Physics body (see `.claude/physics.md`) | | `TRIGGER` | `entitytrigger_t` | Collision trigger zone | ## Accessing component data ```c void *componentGetData( entityid_t entityId, componentid_t componentId, componenttype_t type ); // Returns pointer into the preallocated components pool. // Never NULL for a valid (id, type) pair. ``` Querying all entities with a given type: ```c entityid_t ids[ENTITY_COUNT_MAX]; componentid_t comps[ENTITY_COUNT_MAX]; entityid_t count = componentGetEntitiesWithComponent( COMPONENT_TYPE_PHYSICS, ids, comps ); ``` ## Adding a new component -- checklist 1. Create `src/dusk/entity/component//entity.h/.c`. - Struct: `entity_t` - `entityInit(entityid_t, componentid_t)` (required) - `entityDispose(entityid_t, componentid_t)` (if needed) 2. `#include` the new header in the header block of `componentlist.h`. 3. Add an `X(...)` row in `componentlist.h`. 4. If JS-facing, add a script module (see `CLAUDE.md`). ## Position component (`entityposition_t`) The position component implements the transform hierarchy and uses lazy evaluation with dirty flags to avoid redundant matrix rebuilds. **Dirty flags:** | Flag | Meaning | |------|---------| | `ENTITY_POSITION_FLAG_PRS_DIRTY` | Cached position/rotation/scale stale vs localTransform | | `ENTITY_POSITION_FLAG_ROTATION_DIRTY` | Rotation columns of localTransform stale | | `ENTITY_POSITION_FLAG_POSITION_DIRTY` | Position column of localTransform stale | | `ENTITY_POSITION_FLAG_WORLD_DIRTY` | World matrix stale | **Hierarchy:** up to 8 children per entity. `entityPositionSetParent()` reparents and maintains the child list. `entityDisposeDeep()` / `entityPositionDisposeDeep()` recursively disposes the entire subtree. **Key functions:** ```c // Local space getters/setters (mark local dirty): entityPositionGetLocalPosition / SetLocalPosition entityPositionGetLocalRotation / SetLocalRotation entityPositionGetLocalScale / SetLocalScale // World space getters/setters (ensure world updated): entityPositionGetWorldPosition / SetWorldPosition entityPositionGetWorldRotation / SetWorldRotation entityPositionGetWorldScale / SetWorldScale entityPositionSetParent(entityId, parentEntityId, parentComponentId); entityPositionLookAt(entityId, componentId, eye, target, up); entityPositionRebuild(pos); // force immediate matrix rebuild ``` ## Renderable component (`entityrenderable_t`) Three rendering modes selected via `entityrenderabletype_t`: | Mode | Description | |------|-------------| | `ENTITY_RENDERABLE_TYPE_CUSTOM` | User-supplied `draw` callback | | `ENTITY_RENDERABLE_TYPE_SPRITEBATCH` | Up to 64 sprites + texture | | `ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL` | Up to 8 meshes, shader, material, display state | Default on init: shader material (white, unlit, depth-tested cube). `priority` (int8_t) controls render order; 0 = automatic. ## Callback hooks on entities Entities support up to 5 registered update callbacks and 5 dispose callbacks. These are used by systems that need per-entity ticks without building a full query loop every frame: ```c entityUpdateAdd(entityId, updateFn, componentId, user); entityUpdateRemove(entityId, updateFn); entityDisposeAdd(entityId, disposeFn, componentId, user); entityDisposeRemove(entityId, disposeFn); ``` ## Design rules - Components store **data only**. No logic in a component struct or its init beyond setting default values. - Keep components small and focused. - Cross-component access is fine from a system, but a component must never hold a pointer to another component -- use entity IDs. - Systems must not assume component ordering. Use `componentGetEntitiesWithComponent`.