Quickstart
This page is a tour of the SIECS model. It assumes that siecs.h and siecs.c
are already part of your application; see Building and integrating
for the two supported integration paths.
SIECS uses C17 for the runtime and C++20 for the typed wrapper. Both APIs drive the same active world.
The world owns every entity, component, query, system, resource and observer. Initialize it before any typed operation and destroy it after the application stops.
ecs_init();
/* Create entities, register systems, run frames. */
ecs_fini();ecs::init();
// Create entities, register systems, run frames.
ecs::fini();Handles belong to this world. Do not retain an entity or component pointer after
ecs_fini().
Entities and components
Section titled “Entities and components”An entity is an identity. Components are the data attached to that identity. Declare data once, register it in C, then add or set it on entities.
ECS_COMPONENT(Position, { float x; float y; });ECS_COMPONENT(Velocity, { float x; float y; });ECS_TAG(Enemy);
ECS_COMPONENT_REGISTER(Position);ECS_COMPONENT_REGISTER(Velocity);ECS_COMPONENT_REGISTER(Enemy);
ecs_entity_t enemy = ecs_new();ecs_set(enemy, Position, { 10.0f, 20.0f });ecs_set(enemy, Velocity, { 1.0f, 0.0f });ecs_add(enemy, Enemy);struct Position { float x, y; };struct Velocity { float x, y; };struct Enemy {};
auto enemy = ecs::entity::create() .set(Position{ 10.0f, 20.0f }) .set(Velocity{ 1.0f, 0.0f }) .add<Enemy>();set adds the component when it is absent. get requires it to exist; use
try_get when absence is a valid state.
Systems
Section titled “Systems”A system is a named query callback. SIECS invokes it for every non-empty batch of matching entities when the selected phase runs.
static void Move(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; position[i].y += velocity[i].y; }}
ecs_system({ .name = "Move", .phase = EcsOnUpdate, .query.components = { ecs_inout(Position), ecs_in(Velocity) }, .callback = Move,});ecs::system("Move") .phase(EcsOnUpdate) .each([](Position &position, const Velocity &velocity) { position.x += velocity.x; position.y += velocity.y; });Run the frame with ecs_progress() or ecs::progress(). The query owned by a
system is persistent, so it is the normal choice for repeated work.
Queries
Section titled “Queries”Use a query when you need to iterate data outside a scheduled system. Terms express required, optional, filtered, excluded, inherited or relation data.
ecs_query_id_t moving = ecs_query({ .components = { ecs_inout(Position), ecs_in_optional(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++) { if (velocity) position[i].x += velocity[i].x; }}ecs_query_fini(moving);auto moving = ecs::query() .require<Position>() .optional<Velocity>() .build_handle();
moving.each([](Position &position, ecs::optional<const Velocity> velocity) { if (velocity) position.x += velocity->x;});Optional fields are present for the whole batch or absent for the whole batch.
Do not keep field pointers across a structural change such as add or remove.
Resources
Section titled “Resources”A resource is one typed value for the whole world. It is useful for time, configuration, input, renderer state, or other shared state.
ECS_RESOURCE(Time, { float dt; });ECS_RESOURCE_REGISTER(Time);
ecs_set_resource(Time, { .dt = 1.0f / 60.0f });const Time *time = ecs_get_resource_read(Time);struct Time { float dt; };
ecs::set_resource(Time{ 1.0f / 60.0f });const Time &time = ecs::resource<Time>();Resources are not entity components and do not become query fields. A C++
system can request one directly with ecs::res<const Time>.
Relations and inheritance
Section titled “Relations and inheritance”Relations connect entities. The built-in ChildOf relation models a hierarchy;
custom relations model domain-specific edges such as GroupOf or Targets.
ecs_entity_t parent = ecs_new();ecs_entity_t child = ecs_new();
ecs_relate(child, ChildOf, parent);ecs_entity_t current_parent = ecs_target(child, ChildOf);auto parent = ecs::entity::create();auto child = ecs::entity::create().child_of(parent);
auto current_parent = child.target<ecs::ChildOf>();Inheritance builds reusable abstract bases on top of IsA.
Read-only queries can see an inherited value; writable queries require a local
override.
Observers
Section titled “Observers”Observers react to a specific event instead of running every frame. Built-in events include component add, remove and set, plus relation transitions.
static void OnPositionSet(ecs_observer_event_t *event) { const Position *value = event->trigger_data; printf("x = %f\n", value->x);}
ecs_observer({ .on = EcsOnSet, .query.components = { ecs_in(Position) }, .callback = OnPositionSet,});ecs::observe<ecs::OnSet>().each([](const Position &position) { std::printf("x = %f\n", position.x);});Observer payloads are borrowed for the callback. Use them immediately; do not store pointers to event data.
Continue with a manual
Section titled “Continue with a manual”- Components for type registration, tags, reflection and hooks.
- Queries for every term and iterator rule.
- Systems for phases, ordering and safe mutation.
- Relations and Inheritance for graphs and bases.
- Cookbook for compact patterns.