C entity component system

The SIECS C API.

World ownership, component registration, query terms, and batch iteration remain explicit in the C interface.

Initialize the world

The world owns every entity, archetype table, query, system, resource, and runtime registration. ecs_init() creates the active world and ecs_fini() releases it.

The public API operates on that world. Component identifiers, entity handles, and persistent queries all refer to state owned by the same runtime.

Register components and create entities

ECS_COMPONENT declares a typed data component. ECS_COMPONENT_REGISTER associates that type with a runtime component identifier in the active world.

#include <siecs.h>

ECS_COMPONENT(Position, {
    float x;
    float y;
});

int main(void) {
    ecs_init();
    ECS_COMPONENT_REGISTER(Position);

    ecs_entity_t entity = ecs_new();
    ecs_set(entity, Position, { .x = 10.0f, .y = 20.0f });

    Position *position = ecs_get(entity, Position);
    position->x += 1.0f;

    ecs_fini();
    return 0;
}

ecs_new() creates an entity handle. Setting a component adds it to the entity when needed and writes the value into the destination archetype column.

Define the required component access

Query terms describe both matching and access. In this query, Position is writable and Velocity is read-only.

ecs_query_id_t moving = ecs_query({
    .terms = { ecs_inout(Position), ecs_in(Velocity) },
});

The query caches every archetype table that satisfies those terms. Filter, exclude, and optional terms refine the same matching model.

Iterate contiguous component batches

An iterator walks the query's cached table set. Each successful step exposes one batch, its component columns, and the number of rows in that table.

ecs_iter_t it = ecs_query_iter(moving);
while (ecs_iter_next(&it)) {
    Position *positions = ecs_field(&it, 0);
    const Velocity *velocities = ecs_field(&it, 1);

    for (uint32_t i = 0; i < it.count; i++) {
        positions[i].x += velocities[i].x;
    }
}

Field order follows query-term order. The loop works directly on contiguous component arrays for the current archetype table.

Schedule the query as a system

A system stores a persistent query, a callback, and an execution phase. ecs_progress() runs enabled systems in phase order.

ecs_system({
    .query.terms = {
        ecs_inout(Position),
        ecs_in(Velocity),
    },
    .callback = Move,
    .phase = EcsOnUpdate,
});

The same world can also register observers for events, resources for unique values, relations for entity targets, and modules for grouped imports.

Compile the runtime with the application

The standalone distribution contains siecs.h and siecs.c. Add both files to the project, include the public header, and compile the runtime with the application.

cc -std=c23 -I. main.c siecs.c -pthread -o my_app

The C ABI is also the foundation for the typed C++ API, engine modules, native tools, and additional language integrations.