1418 lines
48 KiB
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);
|
|
}
|