Components
Components are data types registered in a world. They can be used through typed
macros or directly through ecs_component_t ids.
Resources use a separate id space and are stored once per world instead of once per entity. Use a component for per-entity data, and a resource for unique world state such as time, input, renderer state, or shared config.
Typed Components
Section titled “Typed Components”Components are declared once and registered with each world:
ECS_COMPONENT_DECLARE(Position, { float x; float y;});
ECS_COMPONENT_DEFINE(Position);ECS_COMPONENT_REGISTER(world, Position);struct Position { float x, y; };
ecs::init();auto entity = ecs::entity::create();entity.set(Position{ .x = 1.0f, .y = 2.0f });Set And Read
Section titled “Set And Read”ecs_set() adds the component if needed, then writes the value:
ecs_set(entity, Position, { .x = 1.0f, .y = 2.0f });entity.set(Position{ .x = 1.0f, .y = 2.0f });ecs_get() and entity.get<T>() assume the component exists:
Position *position = ecs_get(entity, Position);position->x += 1.0f;Position &position = entity.get<Position>();position.x += 1.0f;When the component may be absent, use the nullable helpers:
Position *position = ecs_try_get(entity, Position);if (position != NULL) position->x += 1.0f;if (Position *position = entity.try_get<Position>()) { position->x += 1.0f;}Add, Remove, Has
Section titled “Add, Remove, Has”Use the typed operation for components that do not need an immediate value:
ecs_add(entity, Position);entity.add<Position>();The C++ methods accept several component types or values at once:
entity.set(Position{ 1.0f, 2.0f }, Velocity{ 3.0f, 4.0f });entity.add<Selected, Visible>();
if (entity.has<Selected, Visible>()) { entity.remove<Selected, Visible>();}Multi-component set, add, and remove calls are deferred as one batch. This
lets SIECS compute the final archetype and migrate the entity once instead of
once per component.
Check and remove components with:
if (ecs_has(entity, Position)) { ecs_remove(entity, Position);}if (entity.has<Position>()) { entity.remove<Position>();}Removing a component that is not present is a no-op.
Disabled
Section titled “Disabled”Disabled is a built-in component. Adding it to an entity excludes that entity
from queries by default:
ecs_add(entity, Disabled);entity.disable();Queries, systems, and observers skip disabled entities by default. To match them explicitly:
ecs_query({ .terms = { ecs_in(Position), ecs_filter(Disabled) },});ecs::query().require<Position>().require<Disabled>().each( [](Position &) { /* process disabled entities */ });Component Requirements
Section titled “Component Requirements”Use ecs_with() when adding one component should also add another component:
ecs_with(ecs_id(Renderable), ecs_id(Transform));
ecs_add(entity, Renderable);
/* Transform was added first. */#include <siecs.h>
ecs_with(ecs_id(Renderable), ecs_id(Transform));
ecs_add(entity, Renderable);
/* Transform was added first. */This only affects future adds. It does not rewrite existing entities.
Requirement cycles are invalid. In debug builds, cycles are asserted when
calling ecs_with().
Id-Based Components
Section titled “Id-Based Components”For generic code, register a component from a descriptor:
typedef struct {
float x;
float y;
} Position;
ecs_component_t position_id = ecs_component({
.name = "Position",
.size = sizeof(Position),
});#include <siecs.h>
typedef struct {
float x;
float y;
} Position;
ecs_component_t position_id = ecs_component({
.name = "Position",
.size = sizeof(Position),
});Then use the _cid functions:
ecs_set_cid(entity, position_id, &(Position){ .x = 1.0f, .y = 2.0f });
Position *position = ecs_get_cid(entity, position_id);#include <siecs.h>
ecs_set_cid(entity, position_id, &(Position){ .x = 1.0f, .y = 2.0f });
Position *position = ecs_get_cid(entity, position_id);Reflection And JSON
Section titled “Reflection And JSON”Typed component macros register reflection metadata through sireflect and
sijson:
ECS_COMPONENT(Position, {
float x;
float y;
});#include <siecs.h>
ECS_COMPONENT(Position, {
float x;
float y;
});That metadata is used by the REST explorer to list component schemas and
serialize reflected component values. Components registered only with a raw
descriptor are reflected only when struct_desc points to a valid
sireflect_struct_desc_t produced by the reflection layer:
ecs_component_t position_id = ecs_component({
.name = "Position",
.size = sizeof(Position),
.struct_desc = position_reflection_desc,
});#include <siecs.h>
ecs_component_t position_id = ecs_component({
.name = "Position",
.size = sizeof(Position),
.struct_desc = position_reflection_desc,
});If a component has no reflection metadata, SIECS can still store and query it, but the REST explorer cannot show or edit its fields.
Component descriptors can provide lifecycle hooks:
static void on_set_position(
ecs_entity_t entity,
ecs_component_t component,
const void *new_value,
void *current_value
) {
const Position *incoming = new_value;
Position *stored = current_value;
(void)world;
(void)entity;
(void)component;
(void)incoming;
(void)stored;
}#include <siecs.h>
static void on_set_position(
ecs_entity_t entity,
ecs_component_t component,
const void *new_value,
void *current_value
) {
const Position *incoming = new_value;
Position *stored = current_value;
(void)world;
(void)entity;
(void)component;
(void)incoming;
(void)stored;
}on_set receives the new value passed to ecs_set() or ecs_set_cid(). The
stored value is still the previous value until the hook returns.
on_add receives zero-initialized storage after the component is added.
on_remove receives the value that is about to be removed.