C entity component system

An ECS API that keeps C explicit.

Register data, describe access with query terms, and schedule systems with a compact interface built for C projects.

A complete C program

The public API makes the world, component registration, system query, and frame progression visible in one source file.

#include <siecs.h>

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

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

static void Move(ecs_iter_t *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;
        positions[i].y += velocities[i].y;
    }
}

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

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

    ecs_entity_t player = ecs_new();
    ecs_set(player, Position, { 0, 0 });
    ecs_set(player, Velocity, { 1, 1 });

    ecs_progress();
    ecs_fini();
    return 0;
}

Register the data your systems use

ECS_COMPONENT describes a C type and its lifecycle metadata. Registering it with the world gives the type a runtime component id that can be used by entities, queries, observers, and tools.

Tags use the same component model when an entity needs a state marker such as Visible, Player, or Selected.

Describe access with query terms

Query terms express both matching and intent. The example writes Position, reads Velocity, requires Visible, and excludes Disabled.

ecs_query_id_t visible = ecs_query({
    .terms = {
        ecs_inout(Position),
        ecs_in(Velocity),
        ecs_filter(Visible),
        ecs_not(Disabled),
    },
});

Persistent queries keep their compatible tables available. Iterators then expose the requested component columns as batches for the callback.

Schedule the work in the frame

A system combines the query with a callback and a phase. Calling ecs_progress() runs enabled systems in phase order, while individual phases and systems can also be run directly by tools or tests.

Resources provide unique world state, observers respond to events, and modules package related registrations behind one import.

Integrate one header and one source file

Copy siecs.h and siecs.c from the standalone distribution, include the public header, and compile them with the application.

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

Continue with the C quick start for Bake integration and the API reference for every public symbol.