Observers and Events
Observers run callbacks when an event is emitted for an entity matching the observer query. They are indexed by event and matching table, so a relation filter can be used without scanning unrelated entities.
Built-in events
Section titled “Built-in events”| Event | Trigger | trigger_data |
|---|---|---|
EcsOnAdd |
A component is added. | Pointer to the added component storage. |
EcsOnRemove |
A component is removed. | Pointer to the component storage before removal. |
EcsOnSet |
A component is set or replaced. | Pointer to the new value passed to ecs_set() or ecs_set_cid(). |
EcsOnRelationSet |
A relation is added or retargeted. | Pointer to ecs_relation_event_t, after the transition. |
EcsOnRelationRemove |
A relation is removed. | Pointer to ecs_relation_event_t, before the transition. |
EcsOnRelationSet is emitted for an addition and for a retarget. Calling
ecs_relate() with the current target is a no-op and emits no relation event.
Deferred relation changes emit when the command buffer is flushed; multiple
deferred changes to one relation are coalesced to the final target.
Observe a component
Section titled “Observe a component”static void on_position_set(ecs_observer_event_t *event) { const Position *value = event->trigger_data; /* value is the new value; storage has not been overwritten yet. */ log_position(event->entity, value);}
ecs_observer({ .on = EcsOnSet, .query = { .components = { ecs_in(Position) } }, .callback = on_position_set,});ecs::observe<ecs::OnSet>().each([](const Position &position) { log_position(position);});Observer queries follow normal component and relation matching rules. Entities
with Disabled or Abstract are excluded by default; mention the component
explicitly when an observer must include them.
ecs_observer({ .on = EcsOnSet, .query = { .components = { ecs_in(Position), ecs_filter(Disabled) }, }, .callback = on_disabled_position_set,});ecs::observe<ecs::OnSet>() .require<Disabled>() .each([](const Position &, const Disabled &) {});Observers are enabled by default. ecs_observer_disable() and
ecs_observer_enable() toggle an observer without unregistering it.
Observe relation transitions
Section titled “Observe relation transitions”The relation payload contains the relation id and both sides of the transition:
relation, old_target, and new_target. The event type is
ecs_relation_event_t in both language APIs.
The values are:
| Operation | old_target |
new_target |
|---|---|---|
| Add | 0 |
New target. |
| Retarget | Previous target. | New target. |
| Remove | Previous target. | 0. |
Use a relation query to observe one relation type:
static void on_group_changed(ecs_observer_event_t *event) { const ecs_relation_event_t *change = event->trigger_data;
if (event->event == EcsOnRelationSet) { log_relation(event->entity, change->old_target, change->new_target); } else { log_relation(event->entity, change->old_target, 0); }}
ecs_observer({ .on = EcsOnRelationSet, .query.relations = { ecs_rel(GroupOf) }, .callback = on_group_changed,});
ecs_observer({ .on = EcsOnRelationRemove, .query.relations = { ecs_rel(GroupOf) }, .callback = on_group_changed,});struct GroupOf {};
auto on_set = ecs::observe<ecs::OnRelationSet>();on_set.with_relation<GroupOf>();on_set.each([](ecs::observer_event event) { const auto *change = event.trigger_data<ecs_relation_event_t>(); log_relation(change->old_target, change->new_target);});
auto on_remove = ecs::observe<ecs::OnRelationRemove>();on_remove.with_relation<GroupOf>();on_remove.each([](ecs::observer_event event) { const auto *change = event.trigger_data<ecs_relation_event_t>(); log_relation(change->old_target, 0);});Set observers match the entity’s new relation table. Remove observers match
the old relation table, like EcsOnRemove matches the component before it is
removed. This means an exact ecs_to() filter can identify the old target for a
remove event and the new target for a set event.
The C++ wrapper provides ecs::OnRelationSet and ecs::OnRelationRemove.
Relation data is read through ecs::observer_event::trigger_data<T>(), as
shown in the C++ tab above.
ecs::observer_event and all trigger_data pointers are borrowed and valid
only during the callback.
Callback context and event payloads
Section titled “Callback context and event payloads”The C callback receives an ecs_observer_event_t with entity, event,
user_data, and trigger_data. The C++ callback receives the corresponding
ecs::observer_event object. Both payloads are borrowed for the duration of
the callback:
Pass a small callback context through user_data:
ecs_observer({ .on = EcsOnSet, .query.components = { ecs_in(Position) }, .callback = on_position_set, .user_data = (uintptr_t)counter_ptr,});uint32_t counter = 0;
ecs::observe<ecs::OnSet>() .user_data(&counter) .each([](ecs::observer_event event, const Position &) { ++*event.user_data<uint32_t>(); });In C++, use user_data() on ecs::observer_event. Component callbacks may
also receive an ecs::observer_event argument when they need the entity,
event id, or callback context.
Custom events
Section titled “Custom events”Create a custom event id with ecs_event() and trigger it explicitly:
ecs_event_t Damaged = ecs_event();
ecs_observer({ .on = Damaged, .query = { .components = { ecs_in(Health) } }, .callback = on_damaged,});
Damage damage = { .amount = 10 };ecs_observer_trigger(entity, Damaged, &damage);struct Damaged {};struct Damage { int amount; };
ecs::observe<Damaged>().each([](Health &health) { health.value -= 1;});
Damage damage{ .amount = 10 };ecs::trigger<Damaged>(entity, &damage);Custom events use the entity’s current table when they are triggered. Their
trigger_data is the pointer supplied to ecs_observer_trigger() or
ecs::trigger().