Skip to content

ECS theory

An ECS separates identity, data and behavior. SIECS is an archetype ECS: it groups entities with the same component set into the same table.

An entity is a small handle. It does not contain Position, Health, a name, or a virtual interface. Those are components or relations attached to the handle.

ecs_entity_t ship = ecs_new();
ecs_set(ship, Position, { 10.0f, 20.0f });
ecs_set(ship, Health, { 100 });

The handle can be passed cheaply. Its generation prevents an old handle from becoming valid again when an entity index is reused.

The component set is the entity’s archetype. These entities occupy different tables because their data shape differs:

Entity Components Table
ship Position, Health Position + Health
asteroid Position Position
enemy Position, Velocity, Health Position + Velocity + Health

Within a table, SIECS stores one contiguous column per component. A system that matches Position + Velocity receives contiguous arrays for both components. That is the normal hot path.

A query is not a per-entity lookup loop. It first selects matching tables, then iterates each table in batches.

ecs_query_id_t moving = ecs_query({
.components = { ecs_inout(Position), ecs_in(Velocity) },
});
ecs_iter_t it = ecs_query_iter(moving);
while (ecs_iter_next(&it)) {
Position *position = ecs_field(&it, 0);
const Velocity *velocity = ecs_field(&it, 1);
for (uint32_t i = 0; i < it.count; i++) {
position[i].x += velocity[i].x;
}
}

The query does not match the Position-only asteroid table. It does match the enemy table, and it processes all matching rows with linear memory access.

A system owns a persistent query and runs it in a phase. Use systems for work that happens repeatedly, such as simulation, transform propagation or render preparation. Use a standalone query for an explicit one-off scan.

world
-> phase
-> system
-> matching table batch
-> component columns

Writing Position.x changes data in place. Adding or removing a component changes the archetype, so the entity moves to another table.

Position *position = ecs_get(entity, Position);
position->x += 1.0f; /* same table */
ecs_add(entity, Velocity); /* Position -> Position + Velocity table */

Migration is a normal ECS operation, but it can invalidate pointers into the current batch. Keep it out of the hot loop where possible, or defer it.

  • A tag is a zero-sized component used as a fact: Enemy, Selected, or Disabled.
  • A resource is one typed value in the world: time, input, configuration.
  • A relation is an edge between entities: ChildOf, GroupOf, Targets.

These are different tools. Choosing the correct one keeps tables compact and queries easy to read.

Read Archetype storage for the storage consequences, then Components, Queries and Systems for the concrete API.