Skip to content

API Reference

This page is an API map, not a second tutorial. Read the Quickstart and the concept manuals for explanations; use this page to find the public operation, its C spelling, and its C++ counterpart.

All examples use the public distribution header. The runtime is C17; the typed wrapper is C++20.

#include <siecs.h>

There is one active world per process. Initialize it before registering types or creating handles, and finalize it after systems and tools stop using them.

ecs_init();
ecs_progress();
ecs_run_phase(EcsOnUpdate);
ecs_quit();
ecs_fini();
Concern C API C++ API
Optional frame cap ecs_init_w_features() ecs::init({ .target_fps = 60 })
Run all enabled systems ecs_progress() ecs::progress()
Run one phase ecs_run_phase() ecs::run_phase()
Stop a loop ecs_quit() ecs::quit()

The world owns entities, component registries, resources, queries, systems, observers and modules. Handles are non-owning and must not outlive the world.

Entities are identities; components are typed data attached to them. The common operations are:

ecs_entity_t player = ecs_new();
ecs_set(player, Position, { .x = 1.0f, .y = 2.0f });
if (ecs_has(player, Position)) {
Position *position = ecs_get(player, Position);
position->x += 1.0f;
}
ecs_remove(player, Position);
ecs_kill(player);
Operation C C++
Create ecs_new(), ecs_new_no_reuse() entity::create(), create_no_reuse()
Test liveness ecs_is_alive() entity::is_alive()
Add/remove ecs_add(), ecs_remove() entity::add<T>(), remove<T>()
Test presence ecs_has() entity::has<T>()
Read, required ecs_get() entity::get<T>()
Read, nullable ecs_try_get() entity::try_get<T>()
Write ecs_set() entity::set()
Destroy ecs_kill() entity::kill()
Disable ecs_add(entity, Disabled) entity::disable()

Register typed declarations once per world with ECS_COMPONENT_REGISTER() in C; native C++ types register on first typed use. Generic code can use the _cid functions (ecs_get_cid, ecs_set_cid, ecs_add_cid, ecs_remove_cid) and ecs::component<T>().

Queries match archetype tables and expose each matching table as a batch. Keep a query id or query_handle for repeated work.

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 *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;
}
}
ecs_query_fini(moving);
Match or access C C++
Required read ecs_in(T) require<T>()
Required write ecs_out(T), ecs_inout(T) non-const callback parameter
Optional read/write ecs_in_optional(T), ecs_inout_optional(T) optional<T>()
Include/exclude without a field ecs_filter(T), ecs_not(T) require<T>(), exclude<T>()
Relation presence ecs_rel(R), ecs_rel_opt(R) with_relation<R>()
Exact target ecs_to(R, target) to<R>(target)
Relation depth ecs_depth(R, depth) depth<R>(depth)
Inherited read ecs_up(T, R) up<T, R>()

Fields are numbered by declaration order; filters and relation terms do not consume a component field index. ecs_field_kind() distinguishes owned, shared, and absent optional fields. Field pointers are borrowed and must not be kept across a structural mutation.

Use ecs_query_each() for one-off C scans. A C query id is released by ecs_query_fini(); a C++ query_handle releases its id automatically.

A system is a persistent query callback attached to a phase. ecs_progress() runs enabled systems in phase order; .after or .after() orders systems in the same phase.

static void move_system(ecs_iter_t *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;
}
}
ecs_system({
.name = "Move",
.phase = EcsOnUpdate,
.query = { .components = { ecs_inout(Position), ecs_in(Velocity) } },
.callback = move_system,
});
Operation C C++
Run all phases ecs_progress() ecs::progress()
Run one phase ecs_run_phase() ecs::run_phase()
Run one system ecs_run_system() ecs::run_system()
Enable/disable ecs_system_enable(), ecs_system_disable() ecs::enable_system(), disable_system()
Phase ecs_phase_t EcsOnUpdate, EcsPostUpdate, etc.

Structural mutations during iteration may migrate an entity and invalidate the current batch pointers. Use deferred mutation when the operation must happen after the current command scope.

Resources are one typed value per world. They are not entity components and do not become query fields.

ecs_set_resource(Time, { .dt = 1.0f / 60.0f });
const Time *time = ecs_get_resource_read(Time);

Use ecs_try_get_resource() or ecs::try_resource<T>() when absence is valid. The id-based C functions use the separate ecs_resource_t registry: ecs_resource_init, ecs_resource_find, ecs_resource_rid, ecs_try_resource_rid, ecs_has_resource_rid, and ecs_remove_resource_rid.

Relations connect a source entity to a target. ChildOf provides hierarchy; IsA provides inheritance from an abstract base.

ecs_relate(child, ChildOf, parent);
ecs_entity_t current_parent = ecs_target(child, ChildOf);
ecs_add(base, Abstract);
ecs_is_a(instance, base);

Register custom relations with ECS_RELATION_DECLARE/DEFINE/REGISTER or ecs::relation<T>(). Storage mode controls which query terms are valid: ecs_to for ByTarget, ecs_depth for ByDepth, and ecs_rel for presence.

Observers react to EcsOnAdd, EcsOnRemove, EcsOnSet, relation transitions, or a custom event. Their payload pointers are borrowed for the callback.

static void on_position_set(ecs_observer_event_t *event) {
const Position *position = event->trigger_data;
log_position(event->entity, position);
}
ecs_observer({
.on = EcsOnSet,
.query = { .components = { ecs_in(Position) } },
.callback = on_position_set,
});

Use ecs_observer_trigger() or ecs::trigger<T>() for custom events. Relation events carry old_target and new_target in ecs_relation_event_t.

Modules group registrations and can enable or disable their captured systems and observers. Imports are idempotent in the active world; the first properties value wins.

ECS_MODULE_DECLARE(physics, { float gravity; });
ECS_MODULE_DEFINE(physics);
void physics_import(const physics_props_t *props) {
(void)props;
ecs_system({ .name = "Move", .callback = move_system });
}
ecs_module_id_t Physics = ECS_MODULE_IMPORT(physics, { .gravity = 9.81f });
ecs_module_disable(Physics);
Family Public C types Public C++ types
Entity ecs_entity_t ecs::entity
Component ecs_component_t, ecs_component_desc_t ecs::component<T>, ecs::component_hooks<T>
Resource ecs_resource_t, ecs_resource_desc_t ecs::resource_handle<T>, ecs::res<T>
Query ecs_query_id_t, ecs_query_desc_t, ecs_iter_t ecs::query, ecs::query_handle
System ecs_system_id_t, ecs_system_desc_t, ecs_phase_t ecs::system
Observer ecs_observer_id_t, ecs_observer_desc_t, ecs_observer_event_t ecs::observer<T>, ecs::observer_event
Module ecs_module_id_t, ecs_module_desc_t ecs::module_ref<T>
Relation ecs_relation_id_t, ecs_relation_desc_t ecs::relation<T>

For lifecycle preconditions, ownership rules, field semantics, deletion policy, and version guarantees, see API stability and the relevant manual. The public headers remain the final authority for overloads and exact descriptor layout.