1028 lines
31 KiB
C
1028 lines
31 KiB
C
/**
|
|
* @file observer.c
|
|
* @brief Observer implementation.
|
|
*
|
|
* The observer implementation contains functions for creating, deleting and
|
|
* invoking observers. The code is split up into single-term observers and
|
|
* multi-term observers. Multi-term observers are created from multiple single-
|
|
* term observers.
|
|
*/
|
|
|
|
#include "private_api.h"
|
|
#include <stddef.h>
|
|
|
|
static
|
|
ecs_entity_t flecs_get_observer_event(
|
|
ecs_term_t *term,
|
|
ecs_entity_t event)
|
|
{
|
|
/* If operator is Not, reverse the event */
|
|
if (term->oper == EcsNot) {
|
|
if (event == EcsOnAdd) {
|
|
event = EcsOnRemove;
|
|
} else if (event == EcsOnRemove) {
|
|
event = EcsOnAdd;
|
|
}
|
|
}
|
|
|
|
return event;
|
|
}
|
|
|
|
static
|
|
ecs_flags32_t flecs_id_flag_for_event(
|
|
ecs_entity_t e)
|
|
{
|
|
if (e == EcsOnAdd) {
|
|
return EcsIdHasOnAdd;
|
|
}
|
|
if (e == EcsOnRemove) {
|
|
return EcsIdHasOnRemove;
|
|
}
|
|
if (e == EcsOnSet) {
|
|
return EcsIdHasOnSet;
|
|
}
|
|
if (e == EcsUnSet) {
|
|
return EcsIdHasUnSet;
|
|
}
|
|
if (e == EcsOnTableFill) {
|
|
return EcsIdHasOnTableFill;
|
|
}
|
|
if (e == EcsOnTableEmpty) {
|
|
return EcsIdHasOnTableEmpty;
|
|
}
|
|
if (e == EcsOnTableCreate) {
|
|
return EcsIdHasOnTableCreate;
|
|
}
|
|
if (e == EcsOnTableDelete) {
|
|
return EcsIdHasOnTableDelete;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
void flecs_inc_observer_count(
|
|
ecs_world_t *world,
|
|
ecs_entity_t event,
|
|
ecs_event_record_t *evt,
|
|
ecs_id_t id,
|
|
int32_t value)
|
|
{
|
|
ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id);
|
|
ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t result = idt->observer_count += value;
|
|
if (result == 1) {
|
|
/* Notify framework that there are observers for the event/id. This
|
|
* allows parts of the code to skip event evaluation early */
|
|
flecs_notify_tables(world, id, &(ecs_table_event_t){
|
|
.kind = EcsTableTriggersForId,
|
|
.event = event
|
|
});
|
|
|
|
ecs_flags32_t flags = flecs_id_flag_for_event(event);
|
|
if (flags) {
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
|
if (idr) {
|
|
idr->flags |= flags;
|
|
}
|
|
}
|
|
} else if (result == 0) {
|
|
/* Ditto, but the reverse */
|
|
flecs_notify_tables(world, id, &(ecs_table_event_t){
|
|
.kind = EcsTableNoTriggersForId,
|
|
.event = event
|
|
});
|
|
|
|
ecs_flags32_t flags = flecs_id_flag_for_event(event);
|
|
if (flags) {
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, id);
|
|
if (idr) {
|
|
idr->flags &= ~flags;
|
|
}
|
|
}
|
|
|
|
flecs_event_id_record_remove(evt, id);
|
|
ecs_os_free(idt);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_register_observer_for_id(
|
|
ecs_world_t *world,
|
|
ecs_observable_t *observable,
|
|
ecs_observer_t *observer,
|
|
size_t offset)
|
|
{
|
|
ecs_id_t term_id = observer->register_id;
|
|
ecs_term_t *term = &observer->filter.terms[0];
|
|
ecs_entity_t trav = term->src.trav;
|
|
|
|
int i;
|
|
for (i = 0; i < observer->event_count; i ++) {
|
|
ecs_entity_t event = flecs_get_observer_event(
|
|
term, observer->events[i]);
|
|
|
|
/* Get observers for event */
|
|
ecs_event_record_t *er = flecs_event_record_ensure(observable, event);
|
|
ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Get observers for (component) id for event */
|
|
ecs_event_id_record_t *idt = flecs_event_id_record_ensure(
|
|
world, er, term_id);
|
|
ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_map_t *observers = ECS_OFFSET(idt, offset);
|
|
ecs_map_init_w_params_if(observers, &world->allocators.ptr);
|
|
ecs_map_insert_ptr(observers, observer->filter.entity, observer);
|
|
|
|
flecs_inc_observer_count(world, event, er, term_id, 1);
|
|
if (trav) {
|
|
flecs_inc_observer_count(world, event, er,
|
|
ecs_pair(trav, EcsWildcard), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_uni_observer_register(
|
|
ecs_world_t *world,
|
|
ecs_observable_t *observable,
|
|
ecs_observer_t *observer)
|
|
{
|
|
ecs_term_t *term = &observer->filter.terms[0];
|
|
ecs_flags32_t flags = term->src.flags;
|
|
|
|
if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) {
|
|
flecs_register_observer_for_id(world, observable, observer,
|
|
offsetof(ecs_event_id_record_t, self_up));
|
|
} else if (flags & EcsSelf) {
|
|
flecs_register_observer_for_id(world, observable, observer,
|
|
offsetof(ecs_event_id_record_t, self));
|
|
} else if (flags & EcsUp) {
|
|
ecs_assert(term->src.trav != 0, ECS_INTERNAL_ERROR, NULL);
|
|
flecs_register_observer_for_id(world, observable, observer,
|
|
offsetof(ecs_event_id_record_t, up));
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_unregister_observer_for_id(
|
|
ecs_world_t *world,
|
|
ecs_observable_t *observable,
|
|
ecs_observer_t *observer,
|
|
size_t offset)
|
|
{
|
|
ecs_id_t term_id = observer->register_id;
|
|
ecs_term_t *term = &observer->filter.terms[0];
|
|
ecs_entity_t trav = term->src.trav;
|
|
|
|
int i;
|
|
for (i = 0; i < observer->event_count; i ++) {
|
|
ecs_entity_t event = flecs_get_observer_event(
|
|
term, observer->events[i]);
|
|
|
|
/* Get observers for event */
|
|
ecs_event_record_t *er = flecs_event_record_get(observable, event);
|
|
ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
/* Get observers for (component) id */
|
|
ecs_event_id_record_t *idt = flecs_event_id_record_get(er, term_id);
|
|
ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
ecs_map_t *id_observers = ECS_OFFSET(idt, offset);
|
|
ecs_map_remove(id_observers, observer->filter.entity);
|
|
if (!ecs_map_count(id_observers)) {
|
|
ecs_map_fini(id_observers);
|
|
}
|
|
|
|
flecs_inc_observer_count(world, event, er, term_id, -1);
|
|
if (trav) {
|
|
flecs_inc_observer_count(world, event, er,
|
|
ecs_pair(trav, EcsWildcard), -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_unregister_observer(
|
|
ecs_world_t *world,
|
|
ecs_observable_t *observable,
|
|
ecs_observer_t *observer)
|
|
{
|
|
ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
if (!observer->filter.terms) {
|
|
ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL);
|
|
return;
|
|
}
|
|
|
|
ecs_term_t *term = &observer->filter.terms[0];
|
|
ecs_flags32_t flags = term->src.flags;
|
|
|
|
if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) {
|
|
flecs_unregister_observer_for_id(world, observable, observer,
|
|
offsetof(ecs_event_id_record_t, self_up));
|
|
} else if (flags & EcsSelf) {
|
|
flecs_unregister_observer_for_id(world, observable, observer,
|
|
offsetof(ecs_event_id_record_t, self));
|
|
} else if (flags & EcsUp) {
|
|
flecs_unregister_observer_for_id(world, observable, observer,
|
|
offsetof(ecs_event_id_record_t, up));
|
|
}
|
|
}
|
|
|
|
static
|
|
bool flecs_ignore_observer(
|
|
ecs_observer_t *observer,
|
|
ecs_table_t *table,
|
|
int32_t evtx)
|
|
{
|
|
ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
|
|
int32_t *last_event_id = observer->last_event_id;
|
|
if (last_event_id && last_event_id[0] == evtx) {
|
|
return true;
|
|
}
|
|
|
|
ecs_flags32_t table_flags = table->flags, filter_flags = observer->filter.flags;
|
|
|
|
bool result = (table_flags & EcsTableIsPrefab) &&
|
|
!(filter_flags & EcsFilterMatchPrefab);
|
|
result = result || ((table_flags & EcsTableIsDisabled) &&
|
|
!(filter_flags & EcsFilterMatchDisabled));
|
|
|
|
return result;
|
|
}
|
|
|
|
static
|
|
bool flecs_is_simple_result(
|
|
ecs_iter_t *it)
|
|
{
|
|
return (it->count == 1) || (it->sizes[0] == 0) || (it->sources[0] == 0);
|
|
}
|
|
|
|
static
|
|
void flecs_observer_invoke(
|
|
ecs_world_t *world,
|
|
ecs_iter_t *it,
|
|
ecs_observer_t *observer,
|
|
ecs_iter_action_t callback,
|
|
int32_t term_index,
|
|
bool simple_result)
|
|
{
|
|
ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
if (ecs_should_log_3()) {
|
|
char *path = ecs_get_fullpath(world, it->system);
|
|
ecs_dbg_3("observer: invoke %s", path);
|
|
ecs_os_free(path);
|
|
}
|
|
|
|
ecs_log_push_3();
|
|
|
|
world->info.observers_ran_frame ++;
|
|
|
|
ecs_filter_t *filter = &observer->filter;
|
|
ecs_assert(term_index < filter->term_count, ECS_INTERNAL_ERROR, NULL);
|
|
ecs_term_t *term = &filter->terms[term_index];
|
|
if (term->oper != EcsNot) {
|
|
ecs_assert((it->offset + it->count) <= ecs_table_count(it->table),
|
|
ECS_INTERNAL_ERROR, NULL);
|
|
}
|
|
|
|
bool instanced = filter->flags & EcsFilterIsInstanced;
|
|
bool match_this = filter->flags & EcsFilterMatchThis;
|
|
bool table_only = it->flags & EcsIterTableOnly;
|
|
if (match_this && (simple_result || instanced || table_only)) {
|
|
callback(it);
|
|
} else {
|
|
ecs_entity_t observer_src = term->src.id;
|
|
if (observer_src && !(term->src.flags & EcsIsEntity)) {
|
|
observer_src = 0;
|
|
}
|
|
|
|
ecs_entity_t *entities = it->entities;
|
|
int32_t i, count = it->count;
|
|
ecs_entity_t src = it->sources[0];
|
|
it->count = 1;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_entity_t e = entities[i];
|
|
it->entities = &e;
|
|
if (!observer_src) {
|
|
callback(it);
|
|
} else if (observer_src == e) {
|
|
ecs_entity_t dummy = 0;
|
|
it->entities = &dummy;
|
|
if (!src) {
|
|
it->sources[0] = e;
|
|
}
|
|
callback(it);
|
|
it->sources[0] = src;
|
|
break;
|
|
}
|
|
}
|
|
it->entities = entities;
|
|
it->count = count;
|
|
}
|
|
|
|
ecs_log_pop_3();
|
|
}
|
|
|
|
static
|
|
void flecs_default_uni_observer_run_callback(ecs_iter_t *it) {
|
|
ecs_observer_t *o = it->ctx;
|
|
it->ctx = o->ctx;
|
|
it->callback = o->callback;
|
|
|
|
if (ecs_should_log_3()) {
|
|
char *path = ecs_get_fullpath(it->world, it->system);
|
|
ecs_dbg_3("observer %s", path);
|
|
ecs_os_free(path);
|
|
}
|
|
|
|
ecs_log_push_3();
|
|
flecs_observer_invoke(it->real_world, it, o, o->callback, 0,
|
|
flecs_is_simple_result(it));
|
|
ecs_log_pop_3();
|
|
}
|
|
|
|
static
|
|
void flecs_uni_observer_invoke(
|
|
ecs_world_t *world,
|
|
ecs_observer_t *observer,
|
|
ecs_iter_t *it,
|
|
ecs_table_t *table,
|
|
ecs_entity_t trav,
|
|
int32_t evtx,
|
|
bool simple_result)
|
|
{
|
|
ecs_filter_t *filter = &observer->filter;
|
|
ecs_term_t *term = &filter->terms[0];
|
|
if (flecs_ignore_observer(observer, table, evtx)) {
|
|
return;
|
|
}
|
|
|
|
ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL);
|
|
if (trav && term->src.trav != trav) {
|
|
return;
|
|
}
|
|
|
|
bool is_filter = term->inout == EcsInOutNone;
|
|
ECS_BIT_COND(it->flags, EcsIterNoData, is_filter);
|
|
it->system = observer->filter.entity;
|
|
it->ctx = observer->ctx;
|
|
it->binding_ctx = observer->binding_ctx;
|
|
it->term_index = observer->term_index;
|
|
it->terms = term;
|
|
|
|
ecs_entity_t event = it->event;
|
|
it->event = flecs_get_observer_event(term, event);
|
|
|
|
if (observer->run) {
|
|
it->next = flecs_default_observer_next_callback;
|
|
it->callback = flecs_default_uni_observer_run_callback;
|
|
it->ctx = observer;
|
|
observer->run(it);
|
|
} else {
|
|
ecs_iter_action_t callback = observer->callback;
|
|
it->callback = callback;
|
|
flecs_observer_invoke(world, it, observer, callback, 0, simple_result);
|
|
}
|
|
|
|
it->event = event;
|
|
}
|
|
|
|
void flecs_observers_invoke(
|
|
ecs_world_t *world,
|
|
ecs_map_t *observers,
|
|
ecs_iter_t *it,
|
|
ecs_table_t *table,
|
|
ecs_entity_t trav,
|
|
int32_t evtx)
|
|
{
|
|
if (ecs_map_is_init(observers)) {
|
|
ecs_table_lock(it->world, table);
|
|
|
|
bool simple_result = flecs_is_simple_result(it);
|
|
ecs_map_iter_t oit = ecs_map_iter(observers);
|
|
while (ecs_map_next(&oit)) {
|
|
ecs_observer_t *o = ecs_map_ptr(&oit);
|
|
ecs_assert(it->table == table, ECS_INTERNAL_ERROR, NULL);
|
|
flecs_uni_observer_invoke(world, o, it, table, trav, evtx, simple_result);
|
|
}
|
|
|
|
ecs_table_unlock(it->world, table);
|
|
}
|
|
}
|
|
|
|
static
|
|
bool flecs_multi_observer_invoke(ecs_iter_t *it) {
|
|
ecs_observer_t *o = it->ctx;
|
|
ecs_world_t *world = it->real_world;
|
|
|
|
if (o->last_event_id[0] == world->event_id) {
|
|
/* Already handled this event */
|
|
return false;
|
|
}
|
|
|
|
o->last_event_id[0] = world->event_id;
|
|
|
|
ecs_iter_t user_it = *it;
|
|
user_it.field_count = o->filter.field_count;
|
|
user_it.terms = o->filter.terms;
|
|
user_it.flags = 0;
|
|
ECS_BIT_COND(user_it.flags, EcsIterNoData,
|
|
ECS_BIT_IS_SET(o->filter.flags, EcsFilterNoData));
|
|
user_it.ids = NULL;
|
|
user_it.columns = NULL;
|
|
user_it.sources = NULL;
|
|
user_it.sizes = NULL;
|
|
user_it.ptrs = NULL;
|
|
|
|
flecs_iter_init(it->world, &user_it, flecs_iter_cache_all);
|
|
user_it.flags |= (it->flags & EcsIterTableOnly);
|
|
|
|
ecs_table_t *table = it->table;
|
|
ecs_table_t *prev_table = it->other_table;
|
|
int32_t pivot_term = it->term_index;
|
|
ecs_term_t *term = &o->filter.terms[pivot_term];
|
|
|
|
int32_t column = it->columns[0];
|
|
if (term->oper == EcsNot) {
|
|
table = it->other_table;
|
|
prev_table = it->table;
|
|
}
|
|
|
|
if (!table) {
|
|
table = &world->store.root;
|
|
}
|
|
if (!prev_table) {
|
|
prev_table = &world->store.root;
|
|
}
|
|
|
|
if (column < 0) {
|
|
column = -column;
|
|
}
|
|
|
|
user_it.columns[0] = 0;
|
|
user_it.columns[pivot_term] = column;
|
|
user_it.sources[pivot_term] = it->sources[0];
|
|
user_it.sizes = o->filter.sizes;
|
|
|
|
if (flecs_filter_match_table(world, &o->filter, table, user_it.ids,
|
|
user_it.columns, user_it.sources, NULL, NULL, false, pivot_term,
|
|
user_it.flags))
|
|
{
|
|
/* Monitor observers only invoke when the filter matches for the first
|
|
* time with an entity */
|
|
if (o->is_monitor) {
|
|
if (flecs_filter_match_table(world, &o->filter, prev_table,
|
|
NULL, NULL, NULL, NULL, NULL, true, -1, user_it.flags))
|
|
{
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* While filter matching needs to be reversed for a Not term, the
|
|
* component data must be fetched from the table we got notified for.
|
|
* Repeat the matching process for the non-matching table so we get the
|
|
* correct column ids and sources, which we need for populate_data */
|
|
if (term->oper == EcsNot) {
|
|
flecs_filter_match_table(world, &o->filter, prev_table, user_it.ids,
|
|
user_it.columns, user_it.sources, NULL, NULL, false, -1,
|
|
user_it.flags | EcsFilterPopulate);
|
|
}
|
|
|
|
flecs_iter_populate_data(world, &user_it, it->table, it->offset,
|
|
it->count, user_it.ptrs);
|
|
|
|
user_it.ptrs[pivot_term] = it->ptrs[0];
|
|
user_it.ids[pivot_term] = it->event_id;
|
|
user_it.system = o->filter.entity;
|
|
user_it.term_index = pivot_term;
|
|
user_it.ctx = o->ctx;
|
|
user_it.binding_ctx = o->binding_ctx;
|
|
user_it.field_count = o->filter.field_count;
|
|
user_it.callback = o->callback;
|
|
|
|
flecs_iter_validate(&user_it);
|
|
ecs_table_lock(it->world, table);
|
|
flecs_observer_invoke(world, &user_it, o, o->callback,
|
|
pivot_term, flecs_is_simple_result(&user_it));
|
|
ecs_table_unlock(it->world, table);
|
|
ecs_iter_fini(&user_it);
|
|
return true;
|
|
}
|
|
|
|
done:
|
|
ecs_iter_fini(&user_it);
|
|
return false;
|
|
}
|
|
|
|
bool ecs_observer_default_run_action(ecs_iter_t *it) {
|
|
ecs_observer_t *o = it->ctx;
|
|
if (o->is_multi) {
|
|
return flecs_multi_observer_invoke(it);
|
|
} else {
|
|
it->ctx = o->ctx;
|
|
ecs_table_lock(it->world, it->table);
|
|
flecs_observer_invoke(it->real_world, it, o, o->callback, 0,
|
|
flecs_is_simple_result(it));
|
|
ecs_table_unlock(it->world, it->table);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_default_multi_observer_run_callback(ecs_iter_t *it) {
|
|
flecs_multi_observer_invoke(it);
|
|
}
|
|
|
|
/* For convenience, so applications can (in theory) use a single run callback
|
|
* that uses ecs_iter_next to iterate results */
|
|
bool flecs_default_observer_next_callback(ecs_iter_t *it) {
|
|
if (it->interrupted_by) {
|
|
return false;
|
|
} else {
|
|
/* Use interrupted_by to signal the next iteration must return false */
|
|
it->interrupted_by = it->system;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Run action for children of multi observer */
|
|
static
|
|
void flecs_multi_observer_builtin_run(ecs_iter_t *it) {
|
|
ecs_observer_t *observer = it->ctx;
|
|
ecs_run_action_t run = observer->run;
|
|
|
|
if (run) {
|
|
it->next = flecs_default_observer_next_callback;
|
|
it->callback = flecs_default_multi_observer_run_callback;
|
|
it->interrupted_by = 0;
|
|
run(it);
|
|
} else {
|
|
flecs_multi_observer_invoke(it);
|
|
}
|
|
}
|
|
|
|
static
|
|
void flecs_uni_observer_yield_existing(
|
|
ecs_world_t *world,
|
|
ecs_observer_t *observer)
|
|
{
|
|
ecs_iter_action_t callback = observer->callback;
|
|
|
|
ecs_defer_begin(world);
|
|
|
|
/* If yield existing is enabled, observer for each thing that matches
|
|
* the event, if the event is iterable. */
|
|
int i, count = observer->event_count;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_entity_t evt = observer->events[i];
|
|
const EcsIterable *iterable = ecs_get(world, evt, EcsIterable);
|
|
if (!iterable) {
|
|
continue;
|
|
}
|
|
|
|
ecs_iter_t it;
|
|
iterable->init(world, world, &it, &observer->filter.terms[0]);
|
|
it.system = observer->filter.entity;
|
|
it.ctx = observer->ctx;
|
|
it.binding_ctx = observer->binding_ctx;
|
|
it.event = evt;
|
|
|
|
ecs_iter_next_action_t next = it.next;
|
|
ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
while (next(&it)) {
|
|
it.event_id = it.ids[0];
|
|
callback(&it);
|
|
}
|
|
}
|
|
|
|
ecs_defer_end(world);
|
|
}
|
|
|
|
static
|
|
void flecs_multi_observer_yield_existing(
|
|
ecs_world_t *world,
|
|
ecs_observer_t *observer)
|
|
{
|
|
ecs_run_action_t run = observer->run;
|
|
if (!run) {
|
|
run = flecs_default_multi_observer_run_callback;
|
|
}
|
|
|
|
ecs_run_aperiodic(world, EcsAperiodicEmptyTables);
|
|
|
|
ecs_defer_begin(world);
|
|
|
|
int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter);
|
|
if (pivot_term < 0) {
|
|
return;
|
|
}
|
|
|
|
/* If yield existing is enabled, invoke for each thing that matches
|
|
* the event, if the event is iterable. */
|
|
int i, count = observer->event_count;
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_entity_t evt = observer->events[i];
|
|
const EcsIterable *iterable = ecs_get(world, evt, EcsIterable);
|
|
if (!iterable) {
|
|
continue;
|
|
}
|
|
|
|
ecs_iter_t it;
|
|
iterable->init(world, world, &it, &observer->filter.terms[pivot_term]);
|
|
it.terms = observer->filter.terms;
|
|
it.field_count = 1;
|
|
it.term_index = pivot_term;
|
|
it.system = observer->filter.entity;
|
|
it.ctx = observer;
|
|
it.binding_ctx = observer->binding_ctx;
|
|
it.event = evt;
|
|
|
|
ecs_iter_next_action_t next = it.next;
|
|
ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
while (next(&it)) {
|
|
it.event_id = it.ids[0];
|
|
run(&it);
|
|
world->event_id ++;
|
|
}
|
|
}
|
|
|
|
ecs_defer_end(world);
|
|
}
|
|
|
|
static
|
|
int flecs_uni_observer_init(
|
|
ecs_world_t *world,
|
|
ecs_observer_t *observer,
|
|
const ecs_observer_desc_t *desc)
|
|
{
|
|
ecs_term_t *term = &observer->filter.terms[0];
|
|
observer->last_event_id = desc->last_event_id;
|
|
if (!observer->last_event_id) {
|
|
observer->last_event_id = &observer->last_event_id_storage;
|
|
}
|
|
observer->register_id = flecs_from_public_id(world, term->id);
|
|
term->field_index = desc->term_index;
|
|
|
|
if (ecs_id_is_tag(world, term->id)) {
|
|
/* If id is a tag, downgrade OnSet/UnSet to OnAdd/OnRemove. */
|
|
int32_t e, count = observer->event_count;
|
|
for (e = 0; e < count; e ++) {
|
|
if (observer->events[e] == EcsOnSet) {
|
|
observer->events[e] = EcsOnAdd;
|
|
} else
|
|
if (observer->events[e] == EcsUnSet) {
|
|
observer->events[e] = EcsOnRemove;
|
|
}
|
|
}
|
|
}
|
|
|
|
flecs_uni_observer_register(world, observer->observable, observer);
|
|
|
|
if (desc->yield_existing) {
|
|
flecs_uni_observer_yield_existing(world, observer);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
int flecs_multi_observer_init(
|
|
ecs_world_t *world,
|
|
ecs_observer_t *observer,
|
|
const ecs_observer_desc_t *desc)
|
|
{
|
|
/* Create last event id for filtering out the same event that arrives from
|
|
* more than one term */
|
|
observer->last_event_id = ecs_os_calloc_t(int32_t);
|
|
|
|
/* Mark observer as multi observer */
|
|
observer->is_multi = true;
|
|
|
|
/* Create a child observer for each term in the filter */
|
|
ecs_filter_t *filter = &observer->filter;
|
|
ecs_observer_desc_t child_desc = *desc;
|
|
child_desc.last_event_id = observer->last_event_id;
|
|
child_desc.run = NULL;
|
|
child_desc.callback = flecs_multi_observer_builtin_run;
|
|
child_desc.ctx = observer;
|
|
child_desc.ctx_free = NULL;
|
|
child_desc.filter.expr = NULL;
|
|
child_desc.filter.terms_buffer = NULL;
|
|
child_desc.filter.terms_buffer_count = 0;
|
|
child_desc.binding_ctx = NULL;
|
|
child_desc.binding_ctx_free = NULL;
|
|
child_desc.yield_existing = false;
|
|
ecs_os_zeromem(&child_desc.entity);
|
|
ecs_os_zeromem(&child_desc.filter.terms);
|
|
ecs_os_memcpy_n(child_desc.events, observer->events,
|
|
ecs_entity_t, observer->event_count);
|
|
|
|
int i, term_count = filter->term_count;
|
|
bool optional_only = filter->flags & EcsFilterMatchThis;
|
|
for (i = 0; i < term_count; i ++) {
|
|
if (filter->terms[i].oper != EcsOptional) {
|
|
if (ecs_term_match_this(&filter->terms[i])) {
|
|
optional_only = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (filter->flags & EcsFilterMatchPrefab) {
|
|
child_desc.filter.flags |= EcsFilterMatchPrefab;
|
|
}
|
|
if (filter->flags & EcsFilterMatchDisabled) {
|
|
child_desc.filter.flags |= EcsFilterMatchDisabled;
|
|
}
|
|
|
|
/* Create observers as children of observer */
|
|
ecs_entity_t old_scope = ecs_set_scope(world, observer->filter.entity);
|
|
|
|
for (i = 0; i < term_count; i ++) {
|
|
if (filter->terms[i].src.flags & EcsFilter) {
|
|
continue;
|
|
}
|
|
|
|
ecs_term_t *term = &child_desc.filter.terms[0];
|
|
child_desc.term_index = filter->terms[i].field_index;
|
|
*term = filter->terms[i];
|
|
|
|
ecs_oper_kind_t oper = term->oper;
|
|
ecs_id_t id = term->id;
|
|
|
|
/* AndFrom & OrFrom terms insert multiple observers */
|
|
if (oper == EcsAndFrom || oper == EcsOrFrom) {
|
|
const ecs_type_t *type = ecs_get_type(world, id);
|
|
int32_t ti, ti_count = type->count;
|
|
ecs_id_t *ti_ids = type->array;
|
|
|
|
/* Correct operator will be applied when an event occurs, and
|
|
* the observer is evaluated on the observer source */
|
|
term->oper = EcsAnd;
|
|
for (ti = 0; ti < ti_count; ti ++) {
|
|
ecs_id_t ti_id = ti_ids[ti];
|
|
ecs_id_record_t *idr = flecs_id_record_get(world, ti_id);
|
|
if (idr->flags & EcsIdDontInherit) {
|
|
continue;
|
|
}
|
|
|
|
term->first.name = NULL;
|
|
term->first.id = ti_ids[ti];
|
|
term->id = ti_ids[ti];
|
|
|
|
if (ecs_observer_init(world, &child_desc) == 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Single component observers never use OR */
|
|
if (oper == EcsOr) {
|
|
term->oper = EcsAnd;
|
|
}
|
|
|
|
/* If observer only contains optional terms, match everything */
|
|
if (optional_only) {
|
|
term->id = EcsAny;
|
|
term->first.id = EcsAny;
|
|
term->src.id = EcsThis;
|
|
term->src.flags = EcsIsVariable;
|
|
term->second.id = 0;
|
|
} else if (term->oper == EcsOptional) {
|
|
continue;
|
|
}
|
|
if (ecs_observer_init(world, &child_desc) == 0) {
|
|
goto error;
|
|
}
|
|
|
|
if (optional_only) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ecs_set_scope(world, old_scope);
|
|
|
|
if (desc->yield_existing) {
|
|
flecs_multi_observer_yield_existing(world, observer);
|
|
}
|
|
|
|
return 0;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
ecs_entity_t ecs_observer_init(
|
|
ecs_world_t *world,
|
|
const ecs_observer_desc_t *desc)
|
|
{
|
|
ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL);
|
|
ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL);
|
|
|
|
ecs_entity_t entity = desc->entity;
|
|
if (!entity) {
|
|
entity = ecs_new(world, 0);
|
|
}
|
|
|
|
EcsPoly *poly = ecs_poly_bind(world, entity, ecs_observer_t);
|
|
if (!poly->poly) {
|
|
ecs_check(desc->callback != NULL || desc->run != NULL,
|
|
ECS_INVALID_OPERATION, NULL);
|
|
|
|
ecs_observer_t *observer = ecs_poly_new(ecs_observer_t);
|
|
ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL);
|
|
observer->dtor = (ecs_poly_dtor_t)flecs_observer_fini;
|
|
|
|
/* Make writeable copy of filter desc so that we can set name. This will
|
|
* make debugging easier, as any error messages related to creating the
|
|
* filter will have the name of the observer. */
|
|
ecs_filter_desc_t filter_desc = desc->filter;
|
|
filter_desc.entity = entity;
|
|
ecs_filter_t *filter = filter_desc.storage = &observer->filter;
|
|
*filter = ECS_FILTER_INIT;
|
|
|
|
/* Parse filter */
|
|
if (ecs_filter_init(world, &filter_desc) == NULL) {
|
|
flecs_observer_fini(observer);
|
|
return 0;
|
|
}
|
|
|
|
/* Observer must have at least one term */
|
|
ecs_check(observer->filter.term_count > 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
poly->poly = observer;
|
|
|
|
ecs_observable_t *observable = desc->observable;
|
|
if (!observable) {
|
|
observable = ecs_get_observable(world);
|
|
}
|
|
|
|
observer->run = desc->run;
|
|
observer->callback = desc->callback;
|
|
observer->ctx = desc->ctx;
|
|
observer->binding_ctx = desc->binding_ctx;
|
|
observer->ctx_free = desc->ctx_free;
|
|
observer->binding_ctx_free = desc->binding_ctx_free;
|
|
observer->term_index = desc->term_index;
|
|
observer->observable = observable;
|
|
|
|
/* Check if observer is monitor. Monitors are created as multi observers
|
|
* since they require pre/post checking of the filter to test if the
|
|
* entity is entering/leaving the monitor. */
|
|
int i;
|
|
for (i = 0; i < FLECS_EVENT_DESC_MAX; i ++) {
|
|
ecs_entity_t event = desc->events[i];
|
|
if (!event) {
|
|
break;
|
|
}
|
|
|
|
if (event == EcsMonitor) {
|
|
/* Monitor event must be first and last event */
|
|
ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
observer->events[0] = EcsOnAdd;
|
|
observer->events[1] = EcsOnRemove;
|
|
observer->event_count ++;
|
|
observer->is_monitor = true;
|
|
} else {
|
|
observer->events[i] = event;
|
|
}
|
|
|
|
observer->event_count ++;
|
|
}
|
|
|
|
/* Observer must have at least one event */
|
|
ecs_check(observer->event_count != 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
bool multi = false;
|
|
|
|
if (filter->term_count == 1 && !desc->last_event_id) {
|
|
ecs_term_t *term = &filter->terms[0];
|
|
/* If the filter has a single term but it is a *From operator, we
|
|
* need to create a multi observer */
|
|
multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom);
|
|
|
|
/* An observer with only optional terms is a special case that is
|
|
* only handled by multi observers */
|
|
multi |= term->oper == EcsOptional;
|
|
}
|
|
|
|
if (filter->term_count == 1 && !observer->is_monitor && !multi) {
|
|
if (flecs_uni_observer_init(world, observer, desc)) {
|
|
goto error;
|
|
}
|
|
} else {
|
|
if (flecs_multi_observer_init(world, observer, desc)) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (ecs_get_name(world, entity)) {
|
|
ecs_trace("#[green]observer#[reset] %s created",
|
|
ecs_get_name(world, entity));
|
|
}
|
|
} else {
|
|
ecs_poly_assert(poly->poly, ecs_observer_t);
|
|
ecs_observer_t *observer = (ecs_observer_t*)poly->poly;
|
|
|
|
if (desc->run) {
|
|
observer->run = desc->run;
|
|
}
|
|
if (desc->callback) {
|
|
observer->callback = desc->callback;
|
|
}
|
|
|
|
if (observer->ctx_free) {
|
|
if (observer->ctx && observer->ctx != desc->ctx) {
|
|
observer->ctx_free(observer->ctx);
|
|
}
|
|
}
|
|
if (observer->binding_ctx_free) {
|
|
if (observer->binding_ctx && observer->binding_ctx != desc->binding_ctx) {
|
|
observer->binding_ctx_free(observer->binding_ctx);
|
|
}
|
|
}
|
|
|
|
if (desc->ctx) {
|
|
observer->ctx = desc->ctx;
|
|
}
|
|
if (desc->binding_ctx) {
|
|
observer->binding_ctx = desc->binding_ctx;
|
|
}
|
|
if (desc->ctx_free) {
|
|
observer->ctx_free = desc->ctx_free;
|
|
}
|
|
if (desc->binding_ctx_free) {
|
|
observer->binding_ctx_free = desc->binding_ctx_free;
|
|
}
|
|
}
|
|
|
|
ecs_poly_modified(world, entity, ecs_observer_t);
|
|
|
|
return entity;
|
|
error:
|
|
ecs_delete(world, entity);
|
|
return 0;
|
|
}
|
|
|
|
void* ecs_observer_get_ctx(
|
|
const ecs_world_t *world,
|
|
ecs_entity_t observer)
|
|
{
|
|
const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t);
|
|
if (o) {
|
|
ecs_poly_assert(o->poly, ecs_observer_t);
|
|
return ((ecs_observer_t*)o->poly)->ctx;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void* ecs_observer_get_binding_ctx(
|
|
const ecs_world_t *world,
|
|
ecs_entity_t observer)
|
|
{
|
|
const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t);
|
|
if (o) {
|
|
ecs_poly_assert(o->poly, ecs_observer_t);
|
|
return ((ecs_observer_t*)o->poly)->binding_ctx;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void flecs_observer_fini(
|
|
ecs_observer_t *observer)
|
|
{
|
|
if (observer->is_multi) {
|
|
ecs_os_free(observer->last_event_id);
|
|
} else {
|
|
if (observer->filter.term_count) {
|
|
flecs_unregister_observer(
|
|
observer->filter.world, observer->observable, observer);
|
|
} else {
|
|
/* Observer creation failed while creating filter */
|
|
}
|
|
}
|
|
|
|
/* Cleanup filters */
|
|
ecs_filter_fini(&observer->filter);
|
|
|
|
/* Cleanup context */
|
|
if (observer->ctx_free) {
|
|
observer->ctx_free(observer->ctx);
|
|
}
|
|
|
|
if (observer->binding_ctx_free) {
|
|
observer->binding_ctx_free(observer->binding_ctx);
|
|
}
|
|
|
|
ecs_poly_free(observer, ecs_observer_t);
|
|
}
|