Files
PixelDefense/engine/libs/flecs/src/observable.c

1418 lines
48 KiB
C

/**
* @file observable.c
* @brief Observable implementation.
*
* The observable implementation contains functions that find the set of
* observers to invoke for an event. The code also contains the implementation
* of a reachable id cache, which is used to speedup event propagation when
* relationships are added/removed to/from entities.
*/
#include "private_api.h"
void flecs_observable_init(
ecs_observable_t *observable)
{
flecs_sparse_init_t(&observable->events, NULL, NULL, ecs_event_record_t);
observable->on_add.event = EcsOnAdd;
observable->on_remove.event = EcsOnRemove;
observable->on_set.event = EcsOnSet;
observable->un_set.event = EcsUnSet;
}
void flecs_observable_fini(
ecs_observable_t *observable)
{
ecs_assert(!ecs_map_is_init(&observable->on_add.event_ids),
ECS_INTERNAL_ERROR, NULL);
ecs_assert(!ecs_map_is_init(&observable->on_remove.event_ids),
ECS_INTERNAL_ERROR, NULL);
ecs_assert(!ecs_map_is_init(&observable->on_set.event_ids),
ECS_INTERNAL_ERROR, NULL);
ecs_assert(!ecs_map_is_init(&observable->un_set.event_ids),
ECS_INTERNAL_ERROR, NULL);
ecs_sparse_t *events = &observable->events;
int32_t i, count = flecs_sparse_count(events);
for (i = 0; i < count; i ++) {
ecs_event_record_t *er =
flecs_sparse_get_dense_t(events, ecs_event_record_t, i);
ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL);
(void)er;
/* All triggers should've unregistered by now */
ecs_assert(!ecs_map_is_init(&er->event_ids),
ECS_INTERNAL_ERROR, NULL);
}
flecs_sparse_fini(&observable->events);
}
ecs_event_record_t* flecs_event_record_get(
const ecs_observable_t *o,
ecs_entity_t event)
{
ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL);
/* Builtin events*/
if (event == EcsOnAdd) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_add);
else if (event == EcsOnRemove) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_remove);
else if (event == EcsOnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_set);
else if (event == EcsUnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->un_set);
else if (event == EcsWildcard) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_wildcard);
/* User events */
return flecs_sparse_try_t(&o->events, ecs_event_record_t, event);
}
ecs_event_record_t* flecs_event_record_ensure(
ecs_observable_t *o,
ecs_entity_t event)
{
ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_event_record_t *er = flecs_event_record_get(o, event);
if (er) {
return er;
}
er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event);
er->event = event;
return er;
}
static
const ecs_event_record_t* flecs_event_record_get_if(
const ecs_observable_t *o,
ecs_entity_t event)
{
ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL);
const ecs_event_record_t *er = flecs_event_record_get(o, event);
if (er) {
if (ecs_map_is_init(&er->event_ids)) {
return er;
}
if (er->any) {
return er;
}
if (er->wildcard) {
return er;
}
if (er->wildcard_pair) {
return er;
}
}
return NULL;
}
ecs_event_id_record_t* flecs_event_id_record_get(
const ecs_event_record_t *er,
ecs_id_t id)
{
if (!er) {
return NULL;
}
if (id == EcsAny) return er->any;
else if (id == EcsWildcard) return er->wildcard;
else if (id == ecs_pair(EcsWildcard, EcsWildcard)) return er->wildcard_pair;
else {
if (ecs_map_is_init(&er->event_ids)) {
return ecs_map_get_deref(&er->event_ids, ecs_event_id_record_t, id);
}
return NULL;
}
}
static
ecs_event_id_record_t* flecs_event_id_record_get_if(
const ecs_event_record_t *er,
ecs_id_t id)
{
ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id);
if (!ider) {
return NULL;
}
if (ider->observer_count) {
return ider;
}
return NULL;
}
ecs_event_id_record_t* flecs_event_id_record_ensure(
ecs_world_t *world,
ecs_event_record_t *er,
ecs_id_t id)
{
ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id);
if (ider) {
return ider;
}
ider = ecs_os_calloc_t(ecs_event_id_record_t);
if (id == EcsAny) {
return er->any = ider;
} else if (id == EcsWildcard) {
return er->wildcard = ider;
} else if (id == ecs_pair(EcsWildcard, EcsWildcard)) {
return er->wildcard_pair = ider;
}
ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr);
ecs_map_insert_ptr(&er->event_ids, id, ider);
return ider;
}
void flecs_event_id_record_remove(
ecs_event_record_t *er,
ecs_id_t id)
{
if (id == EcsAny) {
er->any = NULL;
} else if (id == EcsWildcard) {
er->wildcard = NULL;
} else if (id == ecs_pair(EcsWildcard, EcsWildcard)) {
er->wildcard_pair = NULL;
} else {
ecs_map_remove(&er->event_ids, id);
if (!ecs_map_count(&er->event_ids)) {
ecs_map_fini(&er->event_ids);
}
}
}
static
int32_t flecs_event_observers_get(
const ecs_event_record_t *er,
ecs_id_t id,
ecs_event_id_record_t **iders)
{
if (!er) {
return 0;
}
/* Populate array with observer sets matching the id */
int32_t count = 0;
if (id != EcsAny) {
iders[0] = flecs_event_id_record_get_if(er, EcsAny);
count += iders[count] != 0;
}
iders[count] = flecs_event_id_record_get_if(er, id);
count += iders[count] != 0;
if (id != EcsAny) {
if (ECS_IS_PAIR(id)) {
ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id));
ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard);
ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard);
iders[count] = flecs_event_id_record_get_if(er, id_fwc);
count += iders[count] != 0;
iders[count] = flecs_event_id_record_get_if(er, id_swc);
count += iders[count] != 0;
iders[count] = flecs_event_id_record_get_if(er, id_pwc);
count += iders[count] != 0;
} else {
iders[count] = flecs_event_id_record_get_if(er, EcsWildcard);
count += iders[count] != 0;
}
}
return count;
}
bool flecs_observers_exist(
ecs_observable_t *observable,
ecs_id_t id,
ecs_entity_t event)
{
const ecs_event_record_t *er = flecs_event_record_get_if(observable, event);
if (!er) {
return false;
}
return flecs_event_id_record_get_if(er, id) != NULL;
}
static
void flecs_emit_propagate(
ecs_world_t *world,
ecs_iter_t *it,
ecs_id_record_t *idr,
ecs_id_record_t *tgt_idr,
ecs_entity_t trav,
ecs_event_id_record_t **iders,
int32_t ider_count);
static
void flecs_emit_propagate_id(
ecs_world_t *world,
ecs_iter_t *it,
ecs_id_record_t *idr,
ecs_id_record_t *cur,
ecs_entity_t trav,
ecs_event_id_record_t **iders,
int32_t ider_count)
{
ecs_table_cache_iter_t idt;
if (!flecs_table_cache_all_iter(&cur->cache, &idt)) {
return;
}
const ecs_table_record_t *tr;
while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) {
ecs_table_t *table = tr->hdr.table;
if (!ecs_table_count(table)) {
continue;
}
bool owned = flecs_id_record_get_table(idr, table) != NULL;
int32_t e, entity_count = ecs_table_count(table);
it->table = table;
it->other_table = NULL;
it->offset = 0;
it->count = entity_count;
if (entity_count) {
it->entities = ecs_vec_first(&table->data.entities);
}
/* Treat as new event as this could invoke observers again for
* different tables. */
int32_t evtx = ++ world->event_id;
int32_t ider_i;
for (ider_i = 0; ider_i < ider_count; ider_i ++) {
ecs_event_id_record_t *ider = iders[ider_i];
flecs_observers_invoke(world, &ider->up, it, table, trav, evtx);
if (!owned) {
/* Owned takes precedence */
flecs_observers_invoke(
world, &ider->self_up, it, table, trav, evtx);
}
}
if (!table->_->traversable_count) {
continue;
}
ecs_entity_t *entities = ecs_vec_first(&table->data.entities);
for (e = 0; e < entity_count; e ++) {
ecs_record_t *r = flecs_entities_get(world, entities[e]);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_id_record_t *idr_t = r->idr;
if (idr_t) {
/* Only notify for entities that are used in pairs with
* traversable relationships */
flecs_emit_propagate(world, it, idr, idr_t, trav,
iders, ider_count);
}
}
}
}
static
void flecs_emit_propagate(
ecs_world_t *world,
ecs_iter_t *it,
ecs_id_record_t *idr,
ecs_id_record_t *tgt_idr,
ecs_entity_t propagate_trav,
ecs_event_id_record_t **iders,
int32_t ider_count)
{
ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL);
if (ecs_should_log_3()) {
char *idstr = ecs_id_str(world, tgt_idr->id);
ecs_dbg_3("propagate events/invalidate cache for %s", idstr);
ecs_os_free(idstr);
}
ecs_log_push_3();
/* Propagate to records of traversable relationships */
ecs_id_record_t *cur = tgt_idr;
while ((cur = cur->trav.next)) {
cur->reachable.generation ++; /* Invalidate cache */
/* Get traversed relationship */
ecs_entity_t trav = ECS_PAIR_FIRST(cur->id);
if (propagate_trav && propagate_trav != trav) {
continue;
}
flecs_emit_propagate_id(
world, it, idr, cur, trav, iders, ider_count);
}
ecs_log_pop_3();
}
static
void flecs_emit_propagate_invalidate_tables(
ecs_world_t *world,
ecs_id_record_t *tgt_idr)
{
ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL);
if (ecs_should_log_3()) {
char *idstr = ecs_id_str(world, tgt_idr->id);
ecs_dbg_3("invalidate reachable cache for %s", idstr);
ecs_os_free(idstr);
}
/* Invalidate records of traversable relationships */
ecs_id_record_t *cur = tgt_idr;
while ((cur = cur->trav.next)) {
ecs_reachable_cache_t *rc = &cur->reachable;
if (rc->current != rc->generation) {
/* Subtree is already marked invalid */
continue;
}
rc->generation ++;
ecs_table_cache_iter_t idt;
if (!flecs_table_cache_all_iter(&cur->cache, &idt)) {
continue;
}
const ecs_table_record_t *tr;
while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) {
ecs_table_t *table = tr->hdr.table;
if (!table->_->traversable_count) {
continue;
}
int32_t e, entity_count = ecs_table_count(table);
ecs_entity_t *entities = ecs_vec_first(&table->data.entities);
for (e = 0; e < entity_count; e ++) {
ecs_record_t *r = flecs_entities_get(world, entities[e]);
ecs_id_record_t *idr_t = r->idr;
if (idr_t) {
/* Only notify for entities that are used in pairs with
* traversable relationships */
flecs_emit_propagate_invalidate_tables(world, idr_t);
}
}
}
}
}
void flecs_emit_propagate_invalidate(
ecs_world_t *world,
ecs_table_t *table,
int32_t offset,
int32_t count)
{
ecs_entity_t *entities = ecs_vec_get_t(&table->data.entities,
ecs_entity_t, offset);
int32_t i;
for (i = 0; i < count; i ++) {
ecs_record_t *record = flecs_entities_get(world, entities[i]);
if (!record) {
/* If the event is emitted after a bulk operation, it's possible
* that it hasn't been populated with entities yet. */
continue;
}
ecs_id_record_t *idr_t = record->idr;
if (idr_t) {
/* Event is used as target in traversable relationship, propagate */
flecs_emit_propagate_invalidate_tables(world, idr_t);
}
}
}
static
void flecs_override_copy(
ecs_world_t *world,
ecs_table_t *table,
const ecs_type_info_t *ti,
void *dst,
const void *src,
int32_t offset,
int32_t count)
{
void *ptr = dst;
ecs_copy_t copy = ti->hooks.copy;
ecs_size_t size = ti->size;
int32_t i;
if (copy) {
for (i = 0; i < count; i ++) {
copy(ptr, src, count, ti);
ptr = ECS_OFFSET(ptr, size);
}
} else {
for (i = 0; i < count; i ++) {
ecs_os_memcpy(ptr, src, size);
ptr = ECS_OFFSET(ptr, size);
}
}
ecs_iter_action_t on_set = ti->hooks.on_set;
if (on_set) {
ecs_entity_t *entities = ecs_vec_get_t(
&table->data.entities, ecs_entity_t, offset);
flecs_invoke_hook(world, table, count, offset, entities,
dst, ti->component, ti, EcsOnSet, on_set);
}
}
static
void* flecs_override(
ecs_iter_t *it,
const ecs_type_t *emit_ids,
ecs_id_t id,
ecs_table_t *table,
ecs_id_record_t *idr)
{
if (it->event != EcsOnAdd || (it->flags & EcsEventNoOnSet)) {
return NULL;
}
int32_t i = 0, count = emit_ids->count;
ecs_id_t *ids = emit_ids->array;
for (i = 0; i < count; i ++) {
if (ids[i] == id) {
/* If an id was both inherited and overridden in the same event
* (like what happens during an auto override), we need to copy the
* value of the inherited component to the new component.
* Also flag to the callee that this component was overridden, so
* that an OnSet event can be emmitted for it.
* Note that this is different from a component that was overridden
* after it was inherited, as this does not change the actual value
* of the component for the entity (it is copied from the existing
* overridden component), and does not require an OnSet event. */
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
if (!tr) {
continue;
}
int32_t index = tr->column;
ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL);
ecs_column_t *column = &table->data.columns[index];
ecs_size_t size = column->ti->size;
return ecs_vec_get(&column->data, size, it->offset);
}
}
return NULL;
}
static
void flecs_emit_forward_up(
ecs_world_t *world,
const ecs_event_record_t *er,
const ecs_event_record_t *er_onset,
const ecs_type_t *emit_ids,
ecs_iter_t *it,
ecs_table_t *table,
ecs_id_record_t *idr,
ecs_vec_t *stack,
ecs_vec_t *reachable_ids,
int32_t evtx);
static
void flecs_emit_forward_id(
ecs_world_t *world,
const ecs_event_record_t *er,
const ecs_event_record_t *er_onset,
const ecs_type_t *emit_ids,
ecs_iter_t *it,
ecs_table_t *table,
ecs_id_record_t *idr,
ecs_entity_t tgt,
ecs_table_t *tgt_table,
int32_t column,
int32_t offset,
ecs_entity_t trav,
int32_t evtx)
{
ecs_id_t id = idr->id;
ecs_entity_t event = er ? er->event : 0;
bool inherit = trav == EcsIsA;
bool may_override = inherit && (event == EcsOnAdd) && (emit_ids->count > 1);
ecs_event_id_record_t *iders[5];
ecs_event_id_record_t *iders_onset[5];
/* Skip id if there are no observers for it */
int32_t ider_i, ider_count = flecs_event_observers_get(er, id, iders);
int32_t ider_onset_i, ider_onset_count = 0;
if (er_onset) {
ider_onset_count = flecs_event_observers_get(
er_onset, id, iders_onset);
}
if (!may_override && (!ider_count && !ider_onset_count)) {
return;
}
it->ids[0] = id;
it->sources[0] = tgt;
it->event_id = id;
it->ptrs[0] = NULL;
it->sizes[0] = 0;
int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column);
if (storage_i != -1) {
ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_column_t *c = &tgt_table->data.columns[storage_i];
it->ptrs[0] = ecs_vec_get(&c->data, c->ti->size, offset);
it->sizes[0] = c->ti->size;
}
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
bool owned = tr != NULL;
for (ider_i = 0; ider_i < ider_count; ider_i ++) {
ecs_event_id_record_t *ider = iders[ider_i];
flecs_observers_invoke(world, &ider->up, it, table, trav, evtx);
/* Owned takes precedence */
if (!owned) {
flecs_observers_invoke(world, &ider->self_up, it, table, trav, evtx);
}
}
/* Emit OnSet events for newly inherited components */
if (storage_i != -1) {
bool override = false;
/* If component was added together with IsA relationship, still emit
* OnSet event, as it's a new value for the entity. */
void *base_ptr = it->ptrs[0];
void *ptr = flecs_override(it, emit_ids, id, table, idr);
if (ptr) {
override = true;
it->ptrs[0] = ptr;
}
if (ider_onset_count) {
it->event = er_onset->event;
for (ider_onset_i = 0; ider_onset_i < ider_onset_count; ider_onset_i ++) {
ecs_event_id_record_t *ider = iders_onset[ider_onset_i];
flecs_observers_invoke(world, &ider->up, it, table, trav, evtx);
/* Owned takes precedence */
if (!owned) {
flecs_observers_invoke(
world, &ider->self_up, it, table, trav, evtx);
} else if (override) {
ecs_entity_t src = it->sources[0];
it->sources[0] = 0;
flecs_observers_invoke(world, &ider->self, it, table, 0, evtx);
flecs_observers_invoke(world, &ider->self_up, it, table, 0, evtx);
it->sources[0] = src;
}
}
it->event = event;
it->ptrs[0] = base_ptr;
}
}
}
static
void flecs_emit_forward_and_cache_id(
ecs_world_t *world,
const ecs_event_record_t *er,
const ecs_event_record_t *er_onset,
const ecs_type_t *emit_ids,
ecs_iter_t *it,
ecs_table_t *table,
ecs_id_record_t *idr,
ecs_entity_t tgt,
ecs_record_t *tgt_record,
ecs_table_t *tgt_table,
const ecs_table_record_t *tgt_tr,
int32_t column,
int32_t offset,
ecs_vec_t *reachable_ids,
ecs_entity_t trav,
int32_t evtx)
{
/* Cache forwarded id for (rel, tgt) pair */
ecs_reachable_elem_t *elem = ecs_vec_append_t(&world->allocator,
reachable_ids, ecs_reachable_elem_t);
elem->tr = tgt_tr;
elem->record = tgt_record;
elem->src = tgt;
elem->id = idr->id;
#ifndef NDEBUG
elem->table = tgt_table;
#endif
ecs_assert(tgt_table == tgt_record->table, ECS_INTERNAL_ERROR, NULL);
flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, idr,
tgt, tgt_table, column, offset, trav, evtx);
}
static
int32_t flecs_emit_stack_at(
ecs_vec_t *stack,
ecs_id_record_t *idr)
{
int32_t sp = 0, stack_count = ecs_vec_count(stack);
ecs_table_t **stack_elems = ecs_vec_first(stack);
for (sp = 0; sp < stack_count; sp ++) {
ecs_table_t *elem = stack_elems[sp];
if (flecs_id_record_get_table(idr, elem)) {
break;
}
}
return sp;
}
static
bool flecs_emit_stack_has(
ecs_vec_t *stack,
ecs_id_record_t *idr)
{
return flecs_emit_stack_at(stack, idr) != ecs_vec_count(stack);
}
static
void flecs_emit_forward_cached_ids(
ecs_world_t *world,
const ecs_event_record_t *er,
const ecs_event_record_t *er_onset,
const ecs_type_t *emit_ids,
ecs_iter_t *it,
ecs_table_t *table,
ecs_reachable_cache_t *rc,
ecs_vec_t *reachable_ids,
ecs_vec_t *stack,
ecs_entity_t trav,
int32_t evtx)
{
ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids,
ecs_reachable_elem_t);
int32_t i, count = ecs_vec_count(&rc->ids);
for (i = 0; i < count; i ++) {
ecs_reachable_elem_t *rc_elem = &elems[i];
const ecs_table_record_t *rc_tr = rc_elem->tr;
ecs_assert(rc_tr != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_id_record_t *rc_idr = (ecs_id_record_t*)rc_tr->hdr.cache;
ecs_record_t *rc_record = rc_elem->record;
ecs_assert(rc_idr->id == rc_elem->id, ECS_INTERNAL_ERROR, NULL);
ecs_assert(rc_record != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(flecs_entities_get(world, rc_elem->src) ==
rc_record, ECS_INTERNAL_ERROR, NULL);
ecs_dbg_assert(rc_record->table == rc_elem->table,
ECS_INTERNAL_ERROR, NULL);
if (flecs_emit_stack_has(stack, rc_idr)) {
continue;
}
int32_t rc_offset = ECS_RECORD_TO_ROW(rc_record->row);
flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids,
it, table, rc_idr, rc_elem->src,
rc_record, rc_record->table, rc_tr, rc_tr->index,
rc_offset, reachable_ids, trav, evtx);
}
}
static
void flecs_emit_dump_cache(
ecs_world_t *world,
const ecs_vec_t *vec)
{
ecs_reachable_elem_t *elems = ecs_vec_first_t(vec, ecs_reachable_elem_t);
for (int i = 0; i < ecs_vec_count(vec); i ++) {
ecs_reachable_elem_t *elem = &elems[i];
char *idstr = ecs_id_str(world, elem->id);
char *estr = ecs_id_str(world, elem->src);
ecs_dbg_3("- id: %s (%u), src: %s (%u), table: %p",
idstr, (uint32_t)elem->id,
estr, (uint32_t)elem->src,
elem->table);
ecs_os_free(idstr);
ecs_os_free(estr);
}
if (!ecs_vec_count(vec)) {
ecs_dbg_3("- no entries");
}
}
static
void flecs_emit_forward_table_up(
ecs_world_t *world,
const ecs_event_record_t *er,
const ecs_event_record_t *er_onset,
const ecs_type_t *emit_ids,
ecs_iter_t *it,
ecs_table_t *table,
ecs_entity_t tgt,
ecs_table_t *tgt_table,
ecs_record_t *tgt_record,
ecs_id_record_t *tgt_idr,
ecs_vec_t *stack,
ecs_vec_t *reachable_ids,
int32_t evtx)
{
ecs_allocator_t *a = &world->allocator;
int32_t i, id_count = tgt_table->type.count;
ecs_id_t *ids = tgt_table->type.array;
int32_t offset = ECS_RECORD_TO_ROW(tgt_record->row);
int32_t rc_child_offset = ecs_vec_count(reachable_ids);
int32_t stack_count = ecs_vec_count(stack);
/* If tgt_idr is out of sync but is not the current id record being updated,
* keep track so that we can update two records for the cost of one. */
ecs_reachable_cache_t *rc = &tgt_idr->reachable;
bool parent_revalidate = (reachable_ids != &rc->ids) &&
(rc->current != rc->generation);
if (parent_revalidate) {
ecs_vec_reset_t(a, &rc->ids, ecs_reachable_elem_t);
}
if (ecs_should_log_3()) {
char *idstr = ecs_id_str(world, tgt_idr->id);
ecs_dbg_3("forward events from %s", idstr);
ecs_os_free(idstr);
}
ecs_log_push_3();
/* Function may have to copy values from overridden components if an IsA
* relationship was added together with other components. */
ecs_entity_t trav = ECS_PAIR_FIRST(tgt_idr->id);
bool inherit = trav == EcsIsA;
for (i = 0; i < id_count; i ++) {
ecs_id_t id = ids[i];
ecs_table_record_t *tgt_tr = &tgt_table->_->records[i];
ecs_id_record_t *idr = (ecs_id_record_t*)tgt_tr->hdr.cache;
if (inherit && (idr->flags & EcsIdDontInherit)) {
continue;
}
/* Id has the same relationship, traverse to find ids for forwarding */
if (ECS_PAIR_FIRST(id) == trav) {
ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack,
ecs_table_t*);
t[0] = tgt_table;
ecs_reachable_cache_t *idr_rc = &idr->reachable;
if (idr_rc->current == idr_rc->generation) {
/* Cache hit, use cached ids to prevent traversing the same
* hierarchy multiple times. This especially speeds up code
* where (deep) hierarchies are created. */
if (ecs_should_log_3()) {
char *idstr = ecs_id_str(world, id);
ecs_dbg_3("forward cached for %s", idstr);
ecs_os_free(idstr);
}
ecs_log_push_3();
flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it,
table, idr_rc, reachable_ids, stack, trav, evtx);
ecs_log_pop_3();
} else {
/* Cache is dirty, traverse upwards */
do {
flecs_emit_forward_up(world, er, er_onset, emit_ids, it,
table, idr, stack, reachable_ids, evtx);
if (++i >= id_count) {
break;
}
id = ids[i];
if (ECS_PAIR_FIRST(id) != trav) {
break;
}
} while (true);
}
ecs_vec_remove_last(stack);
continue;
}
int32_t stack_at = flecs_emit_stack_at(stack, idr);
if (parent_revalidate && (stack_at == (stack_count - 1))) {
/* If parent id record needs to be revalidated, add id */
ecs_reachable_elem_t *elem = ecs_vec_append_t(a, &rc->ids,
ecs_reachable_elem_t);
elem->tr = tgt_tr;
elem->record = tgt_record;
elem->src = tgt;
elem->id = idr->id;
#ifndef NDEBUG
elem->table = tgt_table;
#endif
}
/* Skip id if it's masked by a lower table in the tree */
if (stack_at != stack_count) {
continue;
}
flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it,
table, idr, tgt, tgt_record, tgt_table, tgt_tr, i,
offset, reachable_ids, trav, evtx);
}
if (parent_revalidate) {
/* If this is not the current cache being updated, but it's marked
* as out of date, use intermediate results to populate cache. */
int32_t rc_parent_offset = ecs_vec_count(&rc->ids);
/* Only add ids that were added for this table */
int32_t count = ecs_vec_count(reachable_ids);
count -= rc_child_offset;
/* Append ids to any ids that already were added /*/
if (count) {
ecs_vec_grow_t(a, &rc->ids, ecs_reachable_elem_t, count);
ecs_reachable_elem_t *dst = ecs_vec_get_t(&rc->ids,
ecs_reachable_elem_t, rc_parent_offset);
ecs_reachable_elem_t *src = ecs_vec_get_t(reachable_ids,
ecs_reachable_elem_t, rc_child_offset);
ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count);
}
rc->current = rc->generation;
if (ecs_should_log_3()) {
char *idstr = ecs_id_str(world, tgt_idr->id);
ecs_dbg_3("cache revalidated for %s:", idstr);
ecs_os_free(idstr);
flecs_emit_dump_cache(world, &rc->ids);
}
}
ecs_log_pop_3();
}
static
void flecs_emit_forward_up(
ecs_world_t *world,
const ecs_event_record_t *er,
const ecs_event_record_t *er_onset,
const ecs_type_t *emit_ids,
ecs_iter_t *it,
ecs_table_t *table,
ecs_id_record_t *idr,
ecs_vec_t *stack,
ecs_vec_t *reachable_ids,
int32_t evtx)
{
ecs_id_t id = idr->id;
ecs_entity_t tgt = ECS_PAIR_SECOND(id);
tgt = flecs_entities_get_generation(world, tgt);
ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL);
ecs_record_t *tgt_record = flecs_entities_try(world, tgt);
ecs_table_t *tgt_table;
if (!tgt_record || !(tgt_table = tgt_record->table)) {
return;
}
flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table,
tgt, tgt_table, tgt_record, idr, stack, reachable_ids, evtx);
}
static
void flecs_emit_forward(
ecs_world_t *world,
const ecs_event_record_t *er,
const ecs_event_record_t *er_onset,
const ecs_type_t *emit_ids,
ecs_iter_t *it,
ecs_table_t *table,
ecs_id_record_t *idr,
int32_t evtx)
{
ecs_reachable_cache_t *rc = &idr->reachable;
if (rc->current != rc->generation) {
/* Cache miss, iterate the tree to find ids to forward */
if (ecs_should_log_3()) {
char *idstr = ecs_id_str(world, idr->id);
ecs_dbg_3("reachable cache miss for %s", idstr);
ecs_os_free(idstr);
}
ecs_log_push_3();
ecs_vec_t stack;
ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0);
ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t);
flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table,
idr, &stack, &rc->ids, evtx);
it->sources[0] = 0;
ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*);
if (it->event == EcsOnAdd || it->event == EcsOnRemove) {
/* Only OnAdd/OnRemove events can validate top-level cache, which
* is for the id for which the event is emitted.
* The reason for this is that we don't want to validate the cache
* while the administration for the mutated entity isn't up to
* date yet. */
rc->current = rc->generation;
}
if (ecs_should_log_3()) {
ecs_dbg_3("cache after rebuild:");
flecs_emit_dump_cache(world, &rc->ids);
}
ecs_log_pop_3();
} else {
/* Cache hit, use cached values instead of walking the tree */
if (ecs_should_log_3()) {
char *idstr = ecs_id_str(world, idr->id);
ecs_dbg_3("reachable cache hit for %s", idstr);
ecs_os_free(idstr);
flecs_emit_dump_cache(world, &rc->ids);
}
ecs_entity_t trav = ECS_PAIR_FIRST(idr->id);
ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids,
ecs_reachable_elem_t);
int32_t i, count = ecs_vec_count(&rc->ids);
for (i = 0; i < count; i ++) {
ecs_reachable_elem_t *elem = &elems[i];
const ecs_table_record_t *tr = elem->tr;
ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache;
ecs_record_t *r = elem->record;
ecs_assert(rc_idr->id == elem->id, ECS_INTERNAL_ERROR, NULL);
ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_assert(flecs_entities_get(world, elem->src) == r,
ECS_INTERNAL_ERROR, NULL);
ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL);
int32_t offset = ECS_RECORD_TO_ROW(r->row);
flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table,
rc_idr, elem->src, r->table, tr->index, offset, trav, evtx);
}
}
}
/* The emit function is responsible for finding and invoking the observers
* matching the emitted event. The function is also capable of forwarding events
* for newly reachable ids (after adding a relationship) and propagating events
* downwards. Both capabilities are not just useful in application logic, but
* are also an important building block for keeping query caches in sync. */
void flecs_emit(
ecs_world_t *world,
ecs_world_t *stage,
ecs_event_desc_t *desc)
{
ecs_poly_assert(world, ecs_world_t);
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_check(desc->event != 0, ECS_INVALID_PARAMETER, NULL);
ecs_check(desc->event != EcsWildcard, ECS_INVALID_PARAMETER, NULL);
ecs_check(desc->ids != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_check(desc->ids->count != 0, ECS_INVALID_PARAMETER, NULL);
ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL);
ecs_time_t t = {0};
bool measure_time = world->flags & EcsWorldMeasureSystemTime;
if (measure_time) {
ecs_time_measure(&t);
}
const ecs_type_t *ids = desc->ids;
ecs_entity_t event = desc->event;
ecs_table_t *table = desc->table, *other_table = desc->other_table;
int32_t offset = desc->offset;
int32_t i, r, count = desc->count;
ecs_flags32_t table_flags = table->flags;
/* Deferring cannot be suspended for observers */
int32_t defer = world->stages[0].defer;
if (defer < 0) {
world->stages[0].defer *= -1;
}
/* Table events are emitted for internal table operations only, and do not
* provide component data and/or entity ids. */
bool table_event = desc->flags & EcsEventTableOnly;
if (!count && !table_event) {
/* If no count is provided, forward event for all entities in table */
count = ecs_table_count(table) - offset;
}
/* When the NoOnSet flag is provided, no OnSet/UnSet events should be
* generated when new components are inherited. */
bool no_on_set = desc->flags & EcsEventNoOnSet;
ecs_id_t ids_cache = 0;
void *ptrs_cache = NULL;
ecs_size_t sizes_cache = 0;
int32_t columns_cache = 0;
ecs_entity_t sources_cache = 0;
ecs_iter_t it = {
.world = stage,
.real_world = world,
.event = event,
.table = table,
.field_count = 1,
.ids = &ids_cache,
.ptrs = &ptrs_cache,
.sizes = &sizes_cache,
.columns = &columns_cache,
.sources = &sources_cache,
.other_table = other_table,
.offset = offset,
.count = count,
.param = ECS_CONST_CAST(void*, desc->param),
.flags = desc->flags | EcsIterIsValid
};
/* The world event id is used to determine if an observer has already been
* triggered for an event. Observers for multiple components are split up
* into multiple observers for a single component, and this counter is used
* to make sure a multi observer only triggers once, even if multiple of its
* single-component observers trigger. */
int32_t evtx = ++world->event_id;
ecs_observable_t *observable = ecs_get_observable(desc->observable);
ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL);
/* Event records contain all observers for a specific event. In addition to
* the emitted event, also request data for the Wildcard event (for
* observers subscribing to the wildcard event), OnSet and UnSet events. The
* latter to are used for automatically emitting OnSet/UnSet events for
* inherited components, for example when an IsA relationship is added to an
* entity. This doesn't add much overhead, as fetching records is cheap for
* builtin event types. */
const ecs_event_record_t *er = flecs_event_record_get_if(observable, event);
const ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard);
const ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet);
const ecs_event_record_t *er_unset = flecs_event_record_get_if(observable, EcsUnSet);
ecs_data_t *storage = NULL;
ecs_column_t *columns = NULL;
if (count) {
storage = &table->data;
columns = storage->columns;
it.entities = ecs_vec_get_t(&storage->entities, ecs_entity_t, offset);
}
int32_t id_count = ids->count;
ecs_id_t *id_array = ids->array;
/* If a table has IsA relationships, OnAdd/OnRemove events can trigger
* (un)overriding a component. When a component is overridden its value is
* initialized with the value of the overridden component. */
bool can_override = count && (table_flags & EcsTableHasIsA) && (
(event == EcsOnAdd) || (event == EcsOnRemove));
/* When a new (traversable) relationship is added (emitting an OnAdd/OnRemove
* event) this will cause the components of the target entity to be
* propagated to the source entity. This makes it possible for observers to
* get notified of any new reachable components though the relationship. */
bool can_forward = event != EcsOnSet;
/* Set if event has been propagated */
bool propagated = false;
/* Does table has observed entities */
bool has_observed = table_flags & EcsTableHasTraversable;
/* When a relationship is removed, the events reachable through that
* relationship should emit UnSet events. This is part of the behavior that
* allows observers to be agnostic of whether a component is inherited. */
bool can_unset = count && (event == EcsOnRemove) && !no_on_set;
ecs_event_id_record_t *iders[5] = {0};
int32_t unset_count = 0;
repeat_event:
/* This is the core event logic, which is executed for each event. By
* default this is just the event kind from the ecs_event_desc_t struct, but
* can also include the Wildcard and UnSet events. The latter is emitted as
* counterpart to OnSet, for any removed ids associated with data. */
for (i = 0; i < id_count; i ++) {
/* Emit event for each id passed to the function. In most cases this
* will just be one id, like a component that was added, removed or set.
* In some cases events are emitted for multiple ids.
*
* One example is when an id was added with a "With" property, or
* inheriting from a prefab with overrides. In these cases an entity is
* moved directly to the archetype with the additional components. */
ecs_id_record_t *idr = NULL;
const ecs_type_info_t *ti = NULL;
ecs_id_t id = id_array[i];
int32_t ider_i, ider_count = 0;
bool is_pair = ECS_IS_PAIR(id);
void *override_ptr = NULL;
ecs_entity_t base = 0;
/* Check if this id is a pair of an traversable relationship. If so, we
* may have to forward ids from the pair's target. */
if ((can_forward && is_pair) || can_override) {
idr = flecs_query_id_record_get(world, id);
ecs_flags32_t idr_flags = idr->flags;
if (is_pair && (idr_flags & EcsIdTraversable)) {
const ecs_event_record_t *er_fwd = NULL;
if (ECS_PAIR_FIRST(id) == EcsIsA) {
if (event == EcsOnAdd) {
if (!world->stages[0].base) {
/* Adding an IsA relationship can trigger prefab
* instantiation, which can instantiate prefab
* hierarchies for the entity to which the
* relationship was added. */
ecs_entity_t tgt = ECS_PAIR_SECOND(id);
/* Setting this value prevents flecs_instantiate
* from being called recursively, in case prefab
* children also have IsA relationships. */
world->stages[0].base = tgt;
flecs_instantiate(world, tgt, table, offset, count);
world->stages[0].base = 0;
}
/* Adding an IsA relationship will emit OnSet events for
* any new reachable components. */
er_fwd = er_onset;
} else if (event == EcsOnRemove) {
/* Vice versa for removing an IsA relationship. */
er_fwd = er_unset;
}
}
/* Forward events for components from pair target */
flecs_emit_forward(world, er, er_fwd, ids, &it, table, idr, evtx);
}
if (can_override && (!(idr_flags & EcsIdDontInherit))) {
/* Initialize overridden components with value from base */
ti = idr->type_info;
if (ti) {
ecs_table_record_t *base_tr = NULL;
int32_t base_column = ecs_search_relation(world, table,
0, id, EcsIsA, EcsUp, &base, NULL, &base_tr);
if (base_column != -1) {
/* Base found with component */
ecs_table_t *base_table = base_tr->hdr.table;
base_column = base_tr->column;
ecs_assert(base_column != -1, ECS_INTERNAL_ERROR, NULL);
ecs_record_t *base_r = flecs_entities_get(world, base);
ecs_assert(base_r != NULL, ECS_INTERNAL_ERROR, NULL);
int32_t base_row = ECS_RECORD_TO_ROW(base_r->row);
ecs_vec_t *base_v = &base_table->data.columns[base_column].data;
override_ptr = ecs_vec_get(base_v, ti->size, base_row);
}
}
}
}
if (er) {
/* Get observer sets for id. There can be multiple sets of matching
* observers, in case an observer matches for wildcard ids. For
* example, both observers for (ChildOf, p) and (ChildOf, *) would
* match an event for (ChildOf, p). */
ider_count = flecs_event_observers_get(er, id, iders);
idr = idr ? idr : flecs_query_id_record_get(world, id);
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
}
if (can_unset) {
/* Increase UnSet count in case this is a component (has data). This
* will cause the event loop to be ran again as UnSet event. */
idr = idr ? idr : flecs_query_id_record_get(world, id);
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
unset_count += (idr->type_info != NULL);
}
if (!ider_count && !override_ptr) {
/* If nothing more to do for this id, early out */
continue;
}
ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_table_record_t *tr = flecs_id_record_get_table(idr, table);
if (tr == NULL) {
/* When a single batch contains multiple add's for an exclusive
* relationship, it's possible that an id was in the added list
* that is no longer available for the entity. */
continue;
}
int32_t column = tr->index, storage_i;
it.columns[0] = column + 1;
it.ptrs[0] = NULL;
it.sizes[0] = 0;
it.event_id = id;
it.ids[0] = id;
if (count) {
storage_i = tr->column;
if (storage_i != -1) {
/* If this is a component, fetch pointer & size */
ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL);
ecs_column_t *c = &columns[storage_i];
ecs_size_t size = c->ti->size;
void *ptr = ecs_vec_get(&c->data, size, offset);
it.sizes[0] = size;
if (override_ptr) {
if (event == EcsOnAdd) {
/* If this is a new override, initialize the component
* with the value of the overridden component. */
flecs_override_copy(
world, table, ti, ptr, override_ptr, offset, count);
} else if (er_onset) {
/* If an override was removed, this re-exposes the
* overridden component. Because this causes the actual
* (now inherited) value of the component to change, an
* OnSet event must be emitted for the base component.*/
ecs_assert(event == EcsOnRemove, ECS_INTERNAL_ERROR, NULL);
ecs_event_id_record_t *iders_set[5] = {0};
int32_t ider_set_i, ider_set_count =
flecs_event_observers_get(er_onset, id, iders_set);
if (ider_set_count) {
/* Set the source temporarily to the base and base
* component pointer. */
it.sources[0] = base;
it.ptrs[0] = ptr;
for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) {
ecs_event_id_record_t *ider = iders_set[ider_set_i];
flecs_observers_invoke(world, &ider->self_up, &it, table, EcsIsA, evtx);
flecs_observers_invoke(world, &ider->up, &it, table, EcsIsA, evtx);
}
it.sources[0] = 0;
}
}
}
it.ptrs[0] = ptr;
} else {
if (it.event == EcsUnSet) {
/* Only valid for components, not tags */
continue;
}
}
}
/* Actually invoke observers for this event/id */
for (ider_i = 0; ider_i < ider_count; ider_i ++) {
ecs_event_id_record_t *ider = iders[ider_i];
flecs_observers_invoke(world, &ider->self, &it, table, 0, evtx);
flecs_observers_invoke(world, &ider->self_up, &it, table, 0, evtx);
}
if (!ider_count || !count || !has_observed) {
continue;
}
/* If event is propagated, we don't have to manually invalidate entities
* lower in the tree(s). */
propagated = true;
/* The table->traversable_count value indicates if the table contains any
* entities that are used as targets of traversable relationships. If the
* entity/entities for which the event was generated is used as such a
* target, events must be propagated downwards. */
ecs_entity_t *entities = it.entities;
it.entities = NULL;
for (r = 0; r < count; r ++) {
ecs_record_t *record = flecs_entities_get(world, entities[r]);
if (!record) {
/* If the event is emitted after a bulk operation, it's possible
* that it hasn't been populated with entities yet. */
continue;
}
ecs_id_record_t *idr_t = record->idr;
if (idr_t) {
/* Entity is used as target in traversable pairs, propagate */
ecs_entity_t e = entities[r];
it.sources[0] = e;
flecs_emit_propagate(
world, &it, idr, idr_t, 0, iders, ider_count);
}
}
it.table = table;
it.other_table = other_table;
it.entities = entities;
it.count = count;
it.offset = offset;
it.sources[0] = 0;
}
if (count && can_forward && has_observed && !propagated) {
flecs_emit_propagate_invalidate(world, table, offset, count);
}
can_override = false; /* Don't override twice */
can_unset = false; /* Don't unset twice */
can_forward = false; /* Don't forward twice */
if (unset_count && er_unset && (er != er_unset)) {
/* Repeat event loop for UnSet event */
unset_count = 0;
er = er_unset;
it.event = EcsUnSet;
goto repeat_event;
}
if (wcer && er != wcer) {
/* Repeat event loop for Wildcard event */
er = wcer;
it.event = event;
goto repeat_event;
}
error:
world->stages[0].defer = defer;
if (measure_time) {
world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t);
}
return;
}
void ecs_emit(
ecs_world_t *stage,
ecs_event_desc_t *desc)
{
ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage));
if (desc->entity) {
ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL);
ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL);
ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL);
ecs_record_t *r = flecs_entities_get(world, desc->entity);
ecs_table_t *table;
if (!r || !(table = r->table)) {
/* Empty entities can't trigger observers */
return;
}
desc->table = table;
desc->offset = ECS_RECORD_TO_ROW(r->row);
desc->count = 1;
}
if (!desc->observable) {
desc->observable = world;
}
ecs_type_t default_ids = (ecs_type_t){
.count = 1,
.array = (ecs_id_t[]){ EcsAny }
};
if (!desc->ids || !desc->ids->count) {
desc->ids = &default_ids;
}
flecs_emit(world, stage, desc);
}