ECS Theory
SIECS is an archetype ECS. Entities are ids, components are plain data, and systems run logic over batches of entities that share the same component set.
Entities Are Handles
Section titled “Entities Are Handles”An ecs_entity_t identifies a row in world-owned storage. It is not an object
and it does not own behavior. Keep the handle, but store state in components:
ecs_entity_t player = ecs_new();ecs_set(player, Position, { .x = 0, .y = 0 });ecs_set(player, Velocity, { .x = 1, .y = 0 });ecs::init();auto player = ecs::entity::create();player.set(Position{ .x = 0, .y = 0 });player.set(Velocity{ .x = 1, .y = 0 });Entity handles belong to one world. Do not use a handle with a different world.
Components Are Data
Section titled “Components Are Data”A component is a registered type. Data components have a non-zero size, while
tags have size 0 and represent state through presence:
ECS_COMPONENT(Player, {});ecs_add(entity, Player);struct Player {};entity.add<Player>();Use components for per-entity state. Use resources for one value stored on the world, such as time, input, renderer state, or shared configuration.
Archetype Tables
Section titled “Archetype Tables”SIECS stores entities in archetype tables keyed by component set. Adding or
removing a component is a structural change: the entity moves to another table.
Shared component columns are copied, removed columns are dropped, and newly
added data starts zeroed before ecs_set() writes the final value.
This is why iteration is fast: a query visits matching tables and receives contiguous arrays for each requested component.
Queries Match Tables
Section titled “Queries Match Tables”A query does not scan every entity one by one. It stores the matching table ids and returns batches:
ecs_iter_t it = ecs_query_iter(moving);while (ecs_iter_next(&it)) { Position *p = ecs_field(&it, 0); Velocity *v = ecs_field(&it, 1);}ecs::query().each([](Position &p, Velocity &v) { // Each callback invocation processes a matching table row.});ecs_filter(T) and ecs_not(T) affect matching but do not consume field
indexes. Optional terms consume a field index and return NULL for tables where
the component is absent.
Structural Changes During Iteration
Section titled “Structural Changes During Iteration”Writing component values in place is expected. Adding or removing components can move entities between tables and invalidate the current batch pointers. Prefer collecting structural changes and applying them after iteration unless a specific API documents that the operation is safe during iteration.
Disabled Entities
Section titled “Disabled Entities”Adding the built-in Disabled component excludes an entity from queries,
systems, and observers by default. Mention Disabled explicitly when you want
to match disabled entities:
ecs_query({ .terms = { ecs_in(Position), ecs_filter(Disabled) },});ecs::query().require<Position>().require<Disabled>().each( [](Position &) { /* process disabled entities */ });