Files
PixelDefense/engine/libs/flecs/src/observer.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);
}