#include static void Observer(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); } static void Observer_w_value(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); test_int(it->count, 1); test_assert(it->entities != NULL); test_assert(it->entities[0] != 0); Position *p = ecs_field(it, Position, 1); test_int(p->x, 10); test_int(p->y, 20); if (it->field_count > 1) { Velocity *v = ecs_field(it, Velocity, 2); test_int(v->x, 1); test_int(v->y, 2); } } static void Observer_w_1_value(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); test_int(it->count, 1); test_assert(it->entities != NULL); test_assert(it->entities[0] != 0); Position *p = ecs_field(it, Position, 1); test_int(p->x, 10); test_int(p->y, 20); } static void Observer_w_filter_term(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); test_int(it->count, 1); test_assert(it->entities != NULL); test_assert(it->entities[0] != 0); /* Not guaranteed to be NULL */ // test_assert(it->ptrs == NULL); } static void Observer_w_1_filter_term(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); test_int(it->count, 1); test_assert(it->entities != NULL); test_assert(it->entities[0] != 0); test_assert(it->ptrs != NULL); /* Not guaranteed to be NULL */ // test_assert(it->ptrs[0] == NULL); test_assert(it->ptrs[1] != NULL); Velocity *v = ecs_field(it, Velocity, 2); test_int(v->x, 1); test_int(v->y, 2); } static void Observer_w_2_filter_terms(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); test_int(it->count, 1); test_assert(it->entities != NULL); test_assert(it->entities[0] != 0); test_assert(it->ptrs != NULL); test_assert(it->sizes != NULL); /* Not guaranteed to be NULL */ // test_assert(it->ptrs[0] == NULL); // test_assert(it->ptrs[1] == NULL); test_assert(it->ptrs[2] != NULL); Mass *m = ecs_field(it, Mass, 3); test_int(m[0], 100); } static bool dummy_called = false; static void Dummy(ecs_iter_t *it) { dummy_called = true; } void Observer_2_terms_w_on_add(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], TagB); ecs_fini(world); } void Observer_2_terms_w_on_remove(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB}}, .events = {EcsOnRemove}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_remove_id(world, e, TagA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnRemove); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], TagB); ecs_fini(world); } void Observer_2_terms_w_on_set_value(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_id(Position)}, {ecs_id(Velocity)}}, .events = {EcsOnSet}, .callback = Observer_w_value, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_set(world, e, Velocity, {1, 2}); test_int(ctx.invoked, 0); ecs_set(world, e, Position, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.c[0][1], ecs_id(Velocity)); ecs_fini(world); } void Observer_2_terms_w_on_remove_value(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_id(Position)}, {ecs_id(Velocity)}}, .events = {EcsOnRemove}, .callback = Observer_w_value, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_set(world, e, Velocity, {1, 2}); test_int(ctx.invoked, 0); ecs_set(world, e, Position, {10, 20}); test_int(ctx.invoked, 0); ecs_remove(world, e, Position); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnRemove); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.c[0][1], ecs_id(Velocity)); ecs_fini(world); } void Observer_2_terms_w_on_add_2nd(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], TagB); ecs_fini(world); } void Observer_2_terms_w_on_remove_2nd(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB}}, .events = {EcsOnRemove}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_remove_id(world, e, TagB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnRemove); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], TagB); ecs_fini(world); } void Observer_2_pair_terms_w_on_add(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, RelA); ECS_TAG(world, RelB); ECS_TAG(world, ObjA); ECS_TAG(world, ObjB); ecs_id_t pair_a = ecs_pair(RelA, ObjA); ecs_id_t pair_b = ecs_pair(RelB, ObjB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{pair_a}, {pair_b}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_pair(world, e, RelA, ObjA); test_int(ctx.invoked, 0); ecs_add_pair(world, e, RelB, ObjB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], pair_a); test_int(ctx.c[0][1], pair_b); ecs_fini(world); } void Observer_2_pair_terms_w_on_remove(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, RelA); ECS_TAG(world, RelB); ECS_TAG(world, ObjA); ECS_TAG(world, ObjB); ecs_id_t pair_a = ecs_pair(RelA, ObjA); ecs_id_t pair_b = ecs_pair(RelB, ObjB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{pair_a}, {pair_b}}, .events = {EcsOnRemove}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_pair(world, e, RelA, ObjA); test_int(ctx.invoked, 0); ecs_add_pair(world, e, RelB, ObjB); test_int(ctx.invoked, 0); ecs_remove_pair(world, e, RelB, ObjB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnRemove); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], pair_a); test_int(ctx.c[0][1], pair_b); ecs_fini(world); } void Observer_2_wildcard_pair_terms_w_on_add(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, RelA); ECS_TAG(world, RelB); ECS_TAG(world, ObjA); ECS_TAG(world, ObjB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_pair(RelA, ObjA)}, {ecs_pair(RelB, EcsWildcard)}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_pair(world, e, RelA, ObjA); test_int(ctx.invoked, 0); ecs_add_pair(world, e, RelB, ObjB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); test_int(ctx.c[0][1], ecs_pair(RelB, ObjB)); ecs_fini(world); } void Observer_2_wildcard_pair_terms_w_on_add_2_matching(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, RelA); ECS_TAG(world, RelB); ECS_TAG(world, ObjA); ECS_TAG(world, ObjB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_pair(RelA, ObjA)}, {ecs_pair(RelB, EcsWildcard)}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_pair(world, e, RelA, ObjA); test_int(ctx.invoked, 0); ecs_add_pair(world, e, RelB, ObjA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); test_int(ctx.c[0][1], ecs_pair(RelB, ObjA)); ecs_os_zeromem(&ctx); ecs_add_pair(world, e, RelB, ObjB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); test_int(ctx.c[0][1], ecs_pair(RelB, ObjB)); ecs_fini(world); } void Observer_2_wildcard_pair_terms_w_on_add_3_matching(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, RelA); ECS_TAG(world, RelB); ECS_TAG(world, ObjA); ECS_TAG(world, ObjB); ECS_TAG(world, ObjC); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_pair(RelA, ObjA)}, {ecs_pair(RelB, EcsWildcard)}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_pair(world, e, RelA, ObjA); test_int(ctx.invoked, 0); ecs_add_pair(world, e, RelB, ObjA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); test_int(ctx.c[0][1], ecs_pair(RelB, ObjA)); ecs_os_zeromem(&ctx); ecs_add_pair(world, e, RelB, ObjC); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); test_int(ctx.c[0][1], ecs_pair(RelB, ObjC)); ecs_os_zeromem(&ctx); ecs_add_pair(world, e, RelB, ObjB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); test_int(ctx.c[0][1], ecs_pair(RelB, ObjB)); ecs_fini(world); } void Observer_2_wildcard_pair_terms_w_on_remove(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, RelA); ECS_TAG(world, RelB); ECS_TAG(world, ObjA); ECS_TAG(world, ObjB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_pair(RelA, ObjA)}, {ecs_pair(RelB, EcsWildcard)}}, .events = {EcsOnRemove}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_pair(world, e, RelA, ObjA); test_int(ctx.invoked, 0); ecs_add_pair(world, e, RelB, ObjB); test_int(ctx.invoked, 0); ecs_remove_pair(world, e, RelB, ObjB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnRemove); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(RelA, ObjA)); test_int(ctx.c[0][1], ecs_pair(RelB, ObjB)); ecs_fini(world); } void Observer_on_set_n_entities(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Self); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_id(Self)}}, .events = {EcsOnSet}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e_1 = ecs_new_id(world); test_int(ctx.invoked, 0); ecs_set(world, e_1, Self, {e_1}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e_1); test_int(ctx.c[0][0], ecs_id(Self)); ecs_os_zeromem(&ctx); ecs_entity_t e_2 = ecs_new_id(world); test_int(ctx.invoked, 0); ecs_set(world, e_2, Self, {e_2}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e_2); test_int(ctx.c[0][0], ecs_id(Self)); ecs_os_zeromem(&ctx); ecs_entity_t e_3 = ecs_new_id(world); test_int(ctx.invoked, 0); ecs_set(world, e_3, Self, {e_3}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e_3); test_int(ctx.c[0][0], ecs_id(Self)); ecs_fini(world); } void Observer_on_set_n_entities_2_comp(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Obj); ECS_COMPONENT(world, Self); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_id(Self)}, {ecs_pair(ecs_id(Self), EcsWildcard)}}, .events = {EcsOnSet}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e_1 = ecs_new_id(world); ecs_set(world, e_1, Self, {e_1}); test_int(ctx.invoked, 0); ecs_set_pair(world, e_1, Self, Obj, {e_1}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e_1); test_int(ctx.c[0][0], ecs_id(Self)); test_int(ctx.c[0][1], ecs_pair(ecs_id(Self), Obj)); ecs_os_zeromem(&ctx); ecs_entity_t e_2 = ecs_new_id(world); ecs_set(world, e_2, Self, {e_2}); test_int(ctx.invoked, 0); ecs_set_pair(world, e_2, Self, Obj, {e_2}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e_2); test_int(ctx.c[0][0], ecs_id(Self)); test_int(ctx.c[0][1], ecs_pair(ecs_id(Self), Obj)); ecs_fini(world); } void Observer_wildcard_pair_w_pred_component(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, ObjA); ECS_TAG(world, ObjB); ECS_COMPONENT(world, Position); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_pair(ecs_id(Position), EcsWildcard)}}, .events = {EcsOnSet}, .callback = Observer_w_value, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); test_int(ctx.invoked, 0); ecs_set_pair(world, e, Position, ObjA, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), ObjA)); /* Change existing component without triggering OnSet as the callback * expects value {10, 20}, then add a new component with {10, 20} */ Position *p = ecs_get_mut_pair(world, e, Position, ObjA); test_assert(p != NULL); test_int(p->x, 10); test_int(p->y, 20); p->x = 30; p->y = 40; ecs_os_zeromem(&ctx); ecs_set_pair(world, e, Position, ObjB, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), ObjB)); ecs_fini(world); } void Observer_wildcard_pair_w_obj_component(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, RelA); ECS_TAG(world, RelB); ECS_COMPONENT(world, Position); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ ecs_pair(EcsWildcard, ecs_id(Position)) }}, .events = {EcsOnSet}, .callback = Observer_w_value, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); test_int(ctx.invoked, 0); ecs_set_pair_object(world, e, RelA, Position, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(RelA, ecs_id(Position))); /* Change existing component without triggering OnSet as the callback * expects value {10, 20}, then add a new component with {10, 20} */ Position *p = ecs_get_mut_pair_second(world, e, RelA, Position); test_assert(p != NULL); test_int(p->x, 10); test_int(p->y, 20); p->x = 30; p->y = 40; ecs_os_zeromem(&ctx); ecs_set_pair_object(world, e, RelB, Position, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_pair(RelB, ecs_id(Position))); ecs_fini(world); } void Observer_2_terms_1_not_w_on_add(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB, .oper = EcsNot}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_clear(world, e); test_int(ctx.invoked, 0); test_assert(!ecs_has(world, e, TagB)); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], TagB); ecs_fini(world); } void Observer_2_terms_1_not_w_on_remove(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB, .oper = EcsNot}}, .events = {EcsOnRemove}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_remove_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_clear(world, e); test_assert(!ecs_has(world, e, TagB)); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_remove_id(world, e, TagA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnRemove); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], TagB); ecs_fini(world); } void Observer_2_terms_w_on_set(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_id(Position)}, {TagB}}, .events = {EcsOnSet}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_add(world, e, Position); test_int(ctx.invoked, 0); ecs_set(world, e, Position, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnSet); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.c[0][1], TagB); ecs_fini(world); } void Observer_2_terms_w_un_set(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ecs_id(Position)}, {TagB}}, .events = {EcsUnSet}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_add(world, e, Position); test_int(ctx.invoked, 0); ecs_set(world, e, Position, {10, 20}); test_int(ctx.invoked, 0); ecs_remove(world, e, Position); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsUnSet); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.c[0][1], TagB); ecs_fini(world); } void Observer_3_terms_2_or_on_add(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); ECS_TAG(world, TagC); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB, .oper = EcsOr}, {TagC }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], TagB); ecs_os_zeromem(&ctx); ecs_add_id(world, e, TagC); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], TagC); ecs_fini(world); } void Observer_3_terms_2_or_on_remove(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); ECS_TAG(world, TagC); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB, .oper = EcsOr}, {TagC }}, .events = {EcsOnRemove}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagC); test_int(ctx.invoked, 0); ecs_remove_id(world, e, TagC); test_int(ctx.invoked, 1); ecs_remove_id(world, e, TagB); test_int(ctx.invoked, 2); ecs_remove_id(world, e, TagA); test_int(ctx.invoked, 2); ecs_fini(world); } void Observer_2_terms_w_from_entity_on_add(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); ecs_entity_t x = ecs_new_id(world); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB, .src.id = x}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_clear(world, e); test_int(ctx.invoked, 0); ecs_add_id(world, x, TagB); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 1); ecs_fini(world); } void Observer_2_terms_on_remove_on_clear(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB}}, .events = {EcsOnRemove}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_clear(world, e); test_int(ctx.invoked, 1); ecs_fini(world); } void Observer_2_terms_on_remove_on_delete(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{TagA}, {TagB}}, .events = {EcsOnRemove}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_delete(world, e); test_int(ctx.invoked, 1); ecs_fini(world); } void Observer_add_after_delete_observer(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_entity_t observer = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{.id = ecs_id(Position) }}, .events = {EcsOnAdd}, .callback = Dummy }); ecs_entity_t e1 = ecs_new(world, Position); test_assert(e1 != 0); test_assert(ecs_has(world, e1, Position)); test_int(dummy_called, 1); dummy_called = 0; ecs_delete(world, observer); test_int(dummy_called, 0); ecs_entity_t e2 = ecs_new(world, Position); test_assert(e2 != 0); test_assert(ecs_has(world, e2, Position)); test_int(dummy_called, 0); ecs_fini(world); } void Observer_remove_after_delete_observer(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_entity_t observer = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{.id = ecs_id(Position) }}, .events = {EcsOnRemove}, .callback = Dummy }); ecs_entity_t e1 = ecs_new(world, Position); test_assert(e1 != 0); test_assert(ecs_has(world, e1, Position)); test_int(dummy_called, 0); ecs_remove(world, e1, Position); test_int(dummy_called, 1); dummy_called = 0; ecs_delete(world, observer); test_int(dummy_called, 0); ecs_entity_t e2 = ecs_new(world, Position); test_assert(e2 != 0); test_assert(ecs_has(world, e2, Position)); test_int(dummy_called, 0); ecs_remove(world, e2, Position); test_int(dummy_called, 0); ecs_fini(world); } static int ctx_value; static void ctx_free(void *ctx) { test_assert(&ctx_value == ctx); ctx_value ++; } static int binding_ctx_value; static void binding_ctx_free(void *ctx) { test_assert(&binding_ctx_value == ctx); binding_ctx_value ++; } static int ctx_value_2; static void ctx_free_2(void *ctx) { test_assert(&ctx_value_2 == ctx); ctx_value_2 ++; } static int binding_ctx_value_2; static void binding_ctx_free_2(void *ctx) { test_assert(&binding_ctx_value_2 == ctx); binding_ctx_value_2 ++; } void Observer_delete_observer_w_ctx(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Tag); ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ Tag }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx_value, .ctx_free = ctx_free, .binding_ctx = &binding_ctx_value, .binding_ctx_free = binding_ctx_free }); test_assert(o != 0); test_assert(ecs_observer_get_ctx(world, o) == &ctx_value); test_assert(ecs_observer_get_binding_ctx(world, o) == &binding_ctx_value); ecs_delete(world, o); test_int(ctx_value, 1); test_int(binding_ctx_value, 1); ecs_fini(world); } void Observer_update_ctx(void) { ecs_world_t *world = ecs_init(); ECS_TAG(world, Tag); ecs_entity_t system = ecs_observer(world, { .filter.terms = {{.id = Tag}}, .callback = Dummy, .events = {EcsOnAdd}, .ctx = &ctx_value, .ctx_free = ctx_free, .binding_ctx = &binding_ctx_value, .binding_ctx_free = binding_ctx_free }); test_assert(system != 0); test_assert(ecs_observer_get_ctx(world, system) == &ctx_value); test_assert(ecs_observer_get_binding_ctx(world, system) == &binding_ctx_value); ecs_observer(world, { .entity = system, .ctx = &ctx_value, .ctx_free = ctx_free, .binding_ctx = &binding_ctx_value, .binding_ctx_free = binding_ctx_free }); test_int(ctx_value, 0); test_int(binding_ctx_value, 0); test_int(ctx_value_2, 0); test_int(binding_ctx_value_2, 0); ecs_observer(world, { .entity = system, .ctx = &ctx_value_2, .ctx_free = ctx_free_2, .binding_ctx = &binding_ctx_value_2, .binding_ctx_free = binding_ctx_free_2 }); test_int(ctx_value, 1); test_int(binding_ctx_value, 1); test_int(ctx_value_2, 0); test_int(binding_ctx_value_2, 0); ecs_delete(world, system); test_int(ctx_value, 1); test_int(binding_ctx_value, 1); test_int(ctx_value_2, 1); test_int(binding_ctx_value_2, 1); ecs_fini(world); } void Observer_filter_w_strings(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t o = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{.first.name = "TagA"}, {.first.name = "TagB"}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new_id(world); ecs_add_id(world, e, TagB); test_int(ctx.invoked, 0); ecs_add_id(world, e, TagA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], TagB); ecs_fini(world); } static ecs_table_t *trigger_table; void TypeObserver(ecs_iter_t *it) { trigger_table = it->table; } void Observer_iter_type_set(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Tag); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ Tag }}, .events = {EcsOnAdd}, .callback = TypeObserver, .ctx = &ctx }); test_assert(t != 0); ecs_new(world, Tag); test_assert(trigger_table != NULL); test_assert(trigger_table != NULL); const ecs_type_t *type = ecs_table_get_type(trigger_table); test_int(type->count, 1); test_int(type->array[0], Tag); ecs_fini(world); } void ObserverReadonly(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); test_bool(ecs_field_is_readonly(it, 1), true); } void Observer_readonly_term(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA, .inout = EcsIn }}, .events = {EcsOnAdd}, .callback = ObserverReadonly, .ctx = &ctx }); ecs_entity_t e = ecs_new_id(world); test_assert(e != 0); ecs_add(world, e, TagA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.event_id, TagA); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], TagA); ecs_fini(world); } void Observer_trigger_on_prefab(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); Probe ctx_1 = {0}; Probe ctx_2 = {0}; ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx_1 }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }, { EcsPrefab, .oper = EcsOptional}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx_2 }); ecs_new(world, TagA); test_int(ctx_1.invoked, 1); test_int(ctx_2.invoked, 1); ecs_os_zeromem(&ctx_1); ecs_os_zeromem(&ctx_2); ecs_entity_t e = ecs_new_w_id(world, EcsPrefab); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add(world, e, TagA); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 1); ecs_os_zeromem(&ctx_2); e = ecs_new_w_id(world, EcsDisabled); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add(world, e, TagA); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_fini(world); } void Observer_trigger_on_disabled(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); Probe ctx_1 = {0}; Probe ctx_2 = {0}; ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx_1 }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }, { EcsDisabled, .oper = EcsOptional}}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx_2 }); ecs_new(world, TagA); test_int(ctx_1.invoked, 1); test_int(ctx_2.invoked, 1); ecs_os_zeromem(&ctx_1); ecs_os_zeromem(&ctx_2); ecs_entity_t e = ecs_new_w_id(world, EcsPrefab); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add(world, e, TagA); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_os_zeromem(&ctx_2); e = ecs_new_w_id(world, EcsDisabled); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add(world, e, TagA); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 1); ecs_fini(world); } static void UnSet(ecs_iter_t *it) { probe_iter(it); } static void UnSetA(ecs_iter_t *it) { } static void UnSetB(ecs_iter_t *it) { } void Observer_unset_1_of_1(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_OBSERVER(world, UnSet, EcsUnSet, Position); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e = ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_set(world, e, Position, {10, 20}); test_int(ctx.invoked, 0); ecs_set(world, e, Velocity, {1, 2}); test_int(ctx.invoked, 0); ecs_remove(world, e, Velocity); test_int(ctx.invoked, 0); ecs_remove(world, e, Position); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, UnSet); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); ecs_os_zeromem(&ctx); ecs_remove(world, e, Position); test_int(ctx.invoked, 0); ecs_os_zeromem(&ctx); ecs_fini(world); } void Observer_unset_1_of_2(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_OBSERVER(world, UnSet, EcsUnSet, Position, Velocity); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e = ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_set(world, e, Position, {10, 20}); test_int(ctx.invoked, 0); ecs_set(world, e, Velocity, {1, 2}); test_int(ctx.invoked, 0); ecs_remove(world, e, Position); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, UnSet); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][1], ecs_id(Velocity)); test_int(ctx.s[0][1], 0); ecs_os_zeromem(&ctx); ecs_remove(world, e, Velocity); test_int(ctx.invoked, 0); ecs_os_zeromem(&ctx); ecs_remove(world, e, Position); test_int(ctx.invoked, 0); ecs_os_zeromem(&ctx); ecs_fini(world); test_int(ctx.invoked, 0); } void Observer_unset_1_of_3(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_COMPONENT(world, Mass); ECS_OBSERVER(world, UnSet, EcsUnSet, Position, Velocity, Mass); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e = ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_set(world, e, Position, {10, 20}); test_int(ctx.invoked, 0); ecs_set(world, e, Velocity, {1, 2}); test_int(ctx.invoked, 0); ecs_set(world, e, Mass, {1}); test_int(ctx.invoked, 0); ecs_remove(world, e, Position); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, UnSet); test_int(ctx.term_count, 3); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][1], ecs_id(Velocity)); test_int(ctx.s[0][1], 0); test_int(ctx.c[0][2], ecs_id(Mass)); test_int(ctx.s[0][2], 0); ecs_os_zeromem(&ctx); ecs_remove(world, e, Mass); test_int(ctx.invoked, 0); ecs_os_zeromem(&ctx); ecs_remove(world, e, Velocity); test_int(ctx.invoked, 0); ecs_os_zeromem(&ctx); ecs_remove(world, e, Position); test_int(ctx.invoked, 0); ecs_os_zeromem(&ctx); ecs_fini(world); test_int(ctx.invoked, 0); } void Observer_unset_on_delete_1(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_OBSERVER(world, UnSet, EcsUnSet, Position); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_entity_t e = ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_delete(world, e); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, UnSet); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); ecs_fini(world); } void Observer_unset_on_delete_2(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_OBSERVER(world, UnSet, EcsUnSet, Position, Velocity); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e1 = ecs_new(world, Position); ecs_add(world, e1, Velocity); test_int(ctx.invoked, 0); ecs_entity_t e2 = ecs_new(world, Position); ecs_add(world, e2, Velocity); test_int(ctx.invoked, 0); ecs_entity_t e3 = ecs_new(world, Position); ecs_add(world, e3, Velocity); test_int(ctx.invoked, 0); ecs_delete(world, e3); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, UnSet); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e3); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][1], ecs_id(Velocity)); test_int(ctx.s[0][1], 0); ecs_fini(world); } void Observer_unset_on_delete_3(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_COMPONENT(world, Mass); ECS_OBSERVER(world, UnSet, EcsUnSet, Position, Velocity, Mass); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e1 = ecs_new(world, Position); ecs_add(world, e1, Velocity); ecs_add(world, e1, Mass); test_int(ctx.invoked, 0); ecs_entity_t e2 = ecs_new(world, Position); ecs_add(world, e2, Velocity); ecs_add(world, e2, Mass); test_int(ctx.invoked, 0); ecs_entity_t e3 = ecs_new(world, Position); ecs_add(world, e3, Velocity); ecs_add(world, e3, Mass); test_int(ctx.invoked, 0); ecs_delete(world, e3); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, UnSet); test_int(ctx.term_count, 3); test_null(ctx.param); test_int(ctx.e[0], e3); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][1], ecs_id(Velocity)); test_int(ctx.s[0][1], 0); test_int(ctx.c[0][2], ecs_id(Mass)); test_int(ctx.s[0][2], 0); ecs_fini(world); } void Observer_unset_on_fini_1(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_OBSERVER(world, UnSet, EcsUnSet, Position); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e1 = ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_entity_t e2 = ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_entity_t e3 = ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_new(world, Velocity); test_int(ctx.invoked, 0); ecs_fini(world); test_int(ctx.count, 3); test_int(ctx.system, UnSet); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e3); test_int(ctx.e[1], e2); test_int(ctx.e[2], e1); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); } void Observer_unset_on_fini_2(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_OBSERVER(world, UnSet, EcsUnSet, Position, Velocity); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e1 = ecs_new(world, Position); ecs_add(world, e1, Velocity); test_int(ctx.invoked, 0); ecs_entity_t e2 = ecs_new(world, Position); ecs_add(world, e2, Velocity); test_int(ctx.invoked, 0); ecs_entity_t e3 = ecs_new(world, Position); ecs_add(world, e3, Velocity); test_int(ctx.invoked, 0); ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_new(world, Velocity); test_int(ctx.invoked, 0); ecs_fini(world); test_int(ctx.count, 3); test_int(ctx.system, UnSet); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e3); test_int(ctx.e[1], e2); test_int(ctx.e[2], e1); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][1], ecs_id(Velocity)); test_int(ctx.s[0][1], 0); } void Observer_unset_on_fini_3(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_COMPONENT(world, Mass); ECS_OBSERVER(world, UnSet, EcsUnSet, Position, Velocity, Mass); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e1 = ecs_new(world, Position); ecs_add(world, e1, Velocity); ecs_add(world, e1, Mass); test_int(ctx.invoked, 0); ecs_entity_t e2 = ecs_new(world, Position); ecs_add(world, e2, Velocity); ecs_add(world, e2, Mass); test_int(ctx.invoked, 0); ecs_entity_t e3 = ecs_new(world, Position); ecs_add(world, e3, Velocity); ecs_add(world, e3, Mass); test_int(ctx.invoked, 0); ecs_new(world, Position); test_int(ctx.invoked, 0); ecs_new(world, Velocity); test_int(ctx.invoked, 0); ecs_new(world, Mass); test_int(ctx.invoked, 0); ecs_fini(world); test_int(ctx.count, 3); test_int(ctx.system, UnSet); test_int(ctx.term_count, 3); test_null(ctx.param); test_int(ctx.e[0], e3); test_int(ctx.e[1], e2); test_int(ctx.e[2], e1); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][1], ecs_id(Velocity)); test_int(ctx.s[0][1], 0); test_int(ctx.c[0][2], ecs_id(Mass)); test_int(ctx.s[0][2], 0); } void Observer_overlapping_unset_systems(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_COMPONENT(world, Mass); ECS_OBSERVER(world, UnSetA, EcsUnSet, Position); ECS_OBSERVER(world, UnSetB, EcsUnSet, Position, Velocity); ECS_OBSERVER(world, UnSet, EcsUnSet, Position, Velocity); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e = ecs_new(world, Position); ecs_add(world, e, Velocity); test_int(ctx.invoked, 0); ecs_remove(world, e, Velocity); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, UnSet); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][1], ecs_id(Velocity)); test_int(ctx.s[0][1], 0); ecs_fini(world); } static void UnSet_TestComp(ecs_iter_t *it) { if (!ecs_get_ctx(it->world)) { return; } probe_iter(it); test_int(it->count, 1); Position *p = ecs_field(it, Position, 1); test_assert(p != NULL); test_int(p->x, 10); test_int(p->y, 20); } void Observer_unset_move_to_nonempty_table(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_OBSERVER(world, UnSet_TestComp, EcsUnSet, Position); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ECS_ENTITY(world, DummyA, Position, Velocity); test_int(ctx.invoked, 0); ECS_ENTITY(world, DummyB, Position, Velocity); test_int(ctx.invoked, 0); ECS_ENTITY(world, e, Position, Velocity); ecs_set(world, e, Position, {10, 20}); ecs_set(world, e, Velocity, {20, 10}); test_int(ctx.invoked, 0); ecs_entity_t e2 = ecs_new(world, Velocity); ecs_set(world, e2, Velocity, {30, 40}); test_int(ctx.invoked, 0); ecs_remove(world, e, Position); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, UnSet_TestComp); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); /* Prevent system from getting called by fini */ ecs_set_ctx(world, NULL, NULL); ecs_fini(world); } static void UnSet_WriteComp(ecs_iter_t *it) { if (!ecs_get_ctx(it->world)) { return; } probe_iter(it); test_int(it->count, 1); Position *p = ecs_field(it, Position, 1); test_assert(p != NULL); Velocity *v = ecs_field(it, Velocity, 2); test_int(p->x, 10); test_int(p->y, 20); test_int(v->x, 1); test_int(v->y, 2); v->x = 2; v->y = 3; } void Observer_write_in_unset(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_OBSERVER(world, UnSet_WriteComp, EcsUnSet, Position, Velocity); ecs_entity_t e = ecs_set(world, 0, Position, {10, 20}); ecs_set(world, e, Velocity, {1, 2}); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_remove(world, e, Position); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, UnSet_WriteComp); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][1], ecs_id(Velocity)); test_int(ctx.s[0][1], 0); /* Prevent system from getting called by fini */ ecs_set_ctx(world, NULL, NULL); const Velocity *v = ecs_get(world, e, Velocity); test_int(v->x, 2); test_int(v->y, 3); ecs_fini(world); } void Observer_filter_term(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ .id = ecs_id(Position), .inout = EcsInOutNone }}, .events = {EcsOnSet}, .callback = Observer_w_filter_term, .ctx = &ctx }); /* Create entities before trigger */ ecs_entity_t e1 = ecs_set(world, 0, Position, {10, 20}); test_assert(e1 != 0); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, t); test_int(ctx.event, EcsOnSet); test_int(ctx.event_id, ecs_id(Position)); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e1); test_int(ctx.c[0][0], ecs_id(Position)); ecs_fini(world); } void Observer_2_terms_1_filter(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { .id = ecs_id(Position), .inout = EcsInOutNone }, { .id = ecs_id(Velocity) } }, .events = {EcsOnSet}, .callback = Observer_w_1_filter_term, .ctx = &ctx }); /* Create entities before trigger */ ecs_entity_t e1 = ecs_set(world, 0, Velocity, {1, 2}); test_assert(e1 != 0); test_int(ctx.invoked, 0); ecs_set(world, e1, Position, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, t); test_int(ctx.event, EcsOnSet); test_int(ctx.event_id, ecs_id(Position)); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e1); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.c[0][1], ecs_id(Velocity)); ecs_fini(world); } void Observer_3_terms_2_filter(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); ECS_COMPONENT(world, Mass); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { .id = ecs_id(Position), .inout = EcsInOutNone }, { .id = ecs_id(Velocity), .inout = EcsInOutNone }, { .id = ecs_id(Mass) } }, .events = {EcsOnSet}, .callback = Observer_w_2_filter_terms, .ctx = &ctx }); /* Create entities before trigger */ ecs_entity_t e1 = ecs_set(world, 0, Velocity, {1, 2}); test_assert(e1 != 0); test_int(ctx.invoked, 0); ecs_set(world, e1, Mass, {100}); ecs_set(world, e1, Position, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, t); test_int(ctx.event, EcsOnSet); test_int(ctx.event_id, ecs_id(Position)); test_int(ctx.term_count, 3); test_null(ctx.param); test_int(ctx.e[0], e1); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.c[0][1], ecs_id(Velocity)); test_int(ctx.c[0][2], ecs_id(Mass)); ecs_fini(world); } void Observer_and_from(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); ECS_PREFAB(world, Type, TagA, TagB); Probe ctx = {0}; ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { ECS_AND | Type} }, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); ecs_entity_t e = ecs_new(world, 0); test_int(ctx.invoked, 0); ecs_add(world, e, TagA); test_int(ctx.invoked, 0); ecs_add(world, e, TagB); test_int(ctx.invoked, 1); ecs_remove(world, e, TagA); test_int(ctx.invoked, 1); ecs_add(world, e, TagA); test_int(ctx.invoked, 2); ecs_fini(world); } void Observer_or_from(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); ECS_PREFAB(world, Type, TagA, TagB); Probe ctx = {0}; ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { Type, .oper = EcsOrFrom } }, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); ecs_entity_t e = ecs_new(world, 0); test_int(ctx.invoked, 0); ecs_add(world, e, TagA); test_int(ctx.invoked, 1); ecs_add(world, e, TagB); test_int(ctx.invoked, 2); ecs_remove(world, e, TagA); test_int(ctx.invoked, 2); ecs_add(world, e, TagA); test_int(ctx.invoked, 3); ecs_fini(world); } static int invoke_count = 0; static ecs_entity_t base_ent = 0; static ecs_entity_t inst_ent_a = 0; static ecs_entity_t inst_ent_b = 0; static void TriggerTwice(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); test_assert(base_ent != 0); test_assert(inst_ent_a != 0); test_assert(inst_ent_b != 0); test_int(it->count, 1); if (invoke_count == 0) { test_assert(it->entities[0] == base_ent); invoke_count ++; } else if (invoke_count == 1) { test_assert(it->entities[0] == inst_ent_b); invoke_count ++; } else { test_int(invoke_count, 2); test_assert(it->entities[0] == inst_ent_a); invoke_count ++; } } void Observer_notify_propagated_twice(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }}, .events = {EcsOnAdd}, .callback = TriggerTwice, .ctx = &ctx }); test_assert(t1 != 0); base_ent = ecs_new_id(world); inst_ent_a = ecs_new_w_pair(world, EcsIsA, base_ent); ecs_add(world, inst_ent_a, TagB); inst_ent_b = ecs_new_w_pair(world, EcsIsA, base_ent); test_int(ctx.invoked, 0); ecs_add(world, base_ent, TagA); test_int(ctx.invoked, 3); test_int(invoke_count, 3); ecs_fini(world); } void Observer_on_add_yield_existing(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Tag); /* Create entities before trigger */ ecs_entity_t e1 = ecs_new(world, Tag); ecs_entity_t e2 = ecs_new(world, Tag); ecs_entity_t e3 = ecs_new(world, Tag); test_assert(e1 != 0); test_assert(e2 != 0); test_assert(e3 != 0); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ Tag }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx, .yield_existing = true }); test_int(ctx.invoked, 1); test_int(ctx.count, 3); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.event_id, Tag); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e1); test_int(ctx.e[1], e2); test_int(ctx.e[2], e3); test_int(ctx.c[0][0], Tag); // Ensure normal triggering also still works ecs_os_zeromem(&ctx); ecs_new(world, Tag); test_int(ctx.invoked, 1); ecs_fini(world); } void Observer_on_add_yield_existing_2_tables(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); /* Create entities before trigger */ ecs_entity_t e1 = ecs_new(world, TagA); ecs_entity_t e2 = ecs_new(world, TagA); ecs_entity_t e3 = ecs_new(world, TagA); test_assert(e1 != 0); test_assert(e2 != 0); test_assert(e3 != 0); ecs_add(world, e3, TagB); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx, .yield_existing = true }); test_int(ctx.invoked, 2); test_int(ctx.count, 3); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.event_id, TagA); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e1); test_int(ctx.e[1], e2); test_int(ctx.e[2], e3); test_int(ctx.c[0][0], TagA); // Ensure normal triggering also still works ecs_os_zeromem(&ctx); ecs_new(world, TagA); test_int(ctx.invoked, 1); ecs_fini(world); } void Observer_on_add_yield_existing_2_terms(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); ECS_TAG(world, TagC); /* Create entities before trigger */ ecs_entity_t e1 = ecs_new(world, TagA); ecs_entity_t e2 = ecs_new(world, TagA); ecs_entity_t e3 = ecs_new(world, TagA); ecs_entity_t e4 = ecs_new(world, TagA); /* no match */ ecs_entity_t e5 = ecs_new(world, TagB); /* no match */ test_assert(e1 != 0); test_assert(e2 != 0); test_assert(e3 != 0); test_assert(e4 != 0); test_assert(e5 != 0); ecs_add(world, e1, TagB); ecs_add(world, e2, TagB); ecs_add(world, e3, TagB); ecs_add(world, e3, TagC); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }, { TagB }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx, .yield_existing = true }); test_int(ctx.invoked, 2); test_int(ctx.count, 3); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e1); test_int(ctx.e[1], e2); test_int(ctx.e[2], e3); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], TagB); test_int(ctx.c[1][0], TagA); test_int(ctx.c[1][1], TagB); // Ensure normal triggering also still works ecs_os_zeromem(&ctx); ecs_entity_t e6 = ecs_new(world, TagA); test_int(ctx.invoked, 0); ecs_add(world, e6, TagB); test_int(ctx.invoked, 1); ecs_fini(world); } void Observer_on_add_yield_existing_wildcard(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Rel); ECS_TAG(world, TgtA); ECS_TAG(world, TgtB); ecs_entity_t e1 = ecs_new_w_pair(world, Rel, TgtA); ecs_entity_t e2 = ecs_new_w_pair(world, Rel, TgtB); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ ecs_pair(Rel, EcsWildcard ) }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx, .yield_existing = true }); test_int(ctx.invoked, 2); test_int(ctx.count, 2); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e1); test_int(ctx.e[1], e2); test_int(ctx.c[0][0], ecs_pair(Rel, TgtA)); test_int(ctx.c[1][0], ecs_pair(Rel, TgtB)); ecs_fini(world); } void Observer_on_add_yield_existing_wildcard_multi(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Rel); ECS_TAG(world, TgtA); ECS_TAG(world, TgtB); ECS_TAG(world, Tag); ecs_entity_t e1 = ecs_new_w_pair(world, Rel, TgtA); ecs_entity_t e2 = ecs_new_w_pair(world, Rel, TgtB); ecs_new_w_pair(world, Rel, TgtB); ecs_add(world, e1, Tag); ecs_add(world, e2, Tag); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ ecs_pair(Rel, EcsWildcard ) }, { Tag }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx, .yield_existing = true }); test_int(ctx.invoked, 2); test_int(ctx.count, 2); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e1); test_int(ctx.e[1], e2); test_int(ctx.c[0][0], ecs_pair(Rel, TgtA)); test_int(ctx.c[0][1], Tag); test_int(ctx.c[1][0], ecs_pair(Rel, TgtB)); test_int(ctx.c[1][1], Tag); ecs_fini(world); } void Observer_on_add_yield_existing_wildcard_multi_w_wildcard_pivot(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Rel); ECS_TAG(world, TgtA); ECS_TAG(world, TgtB); ECS_TAG(world, Tag); ecs_entity_t e1 = ecs_new_w_pair(world, Rel, TgtA); ecs_entity_t e2 = ecs_new_w_pair(world, Rel, TgtB); ecs_add(world, e1, Tag); ecs_add(world, e2, Tag); ecs_new(world, Tag); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ ecs_pair(Rel, EcsWildcard ) }, { Tag }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx, .yield_existing = true }); test_int(ctx.invoked, 2); test_int(ctx.count, 2); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 2); test_null(ctx.param); test_int(ctx.e[0], e1); test_int(ctx.e[1], e2); test_int(ctx.c[0][0], ecs_pair(Rel, TgtA)); test_int(ctx.c[0][1], Tag); test_int(ctx.c[1][0], ecs_pair(Rel, TgtB)); test_int(ctx.c[1][1], Tag); ecs_fini(world); } void Observer_observer_superset_wildcard(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Rel); ECS_TAG(world, ObjA); ECS_TAG(world, ObjB); ecs_entity_t base = ecs_new_id(world); ecs_entity_t inst = ecs_new_id(world); ecs_add_pair(world, inst, EcsIsA, base); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ .id = ecs_pair(Rel, EcsWildcard), .src.flags = EcsUp }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(t != 0); test_int(ctx.invoked, 0); ecs_add_pair(world, base, Rel, ObjA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.event_id, ecs_pair(Rel, ObjA)); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], inst); test_int(ctx.s[0][0], base); ecs_os_zeromem(&ctx); ecs_add_pair(world, base, Rel, ObjB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.event_id, ecs_pair(Rel, ObjB)); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], inst); test_int(ctx.s[0][0], base); ecs_fini(world); } void Observer_observer_superset_wildcard_add_isa(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Rel); ECS_TAG(world, ObjA); ECS_TAG(world, ObjB); ecs_entity_t base = ecs_new_id(world); ecs_add_pair(world, base, Rel, ObjA); ecs_add_pair(world, base, Rel, ObjB); ecs_entity_t inst = ecs_new_id(world); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ .id = ecs_pair(Rel, EcsWildcard), .src.flags = EcsUp }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(t != 0); test_int(ctx.invoked, 0); ecs_add_pair(world, inst, EcsIsA, base); test_int(ctx.invoked, 2); test_int(ctx.count, 2); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], inst); test_int(ctx.s[0][0], base); ecs_fini(world); } void Observer_observer_superset_wildcard_add_isa_at_offset(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Tag); ECS_TAG(world, Rel); ECS_TAG(world, ObjA); ECS_TAG(world, ObjB); ecs_entity_t base = ecs_new_id(world); ecs_add(world, base, Tag); ecs_add_pair(world, base, Rel, ObjA); ecs_add_pair(world, base, Rel, ObjB); ecs_entity_t inst = ecs_new_id(world); Probe ctx = {0}; ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ .id = ecs_pair(Rel, EcsWildcard), .src.flags = EcsUp }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); test_assert(t != 0); test_int(ctx.invoked, 0); ecs_add_pair(world, inst, EcsIsA, base); test_int(ctx.invoked, 2); test_int(ctx.count, 2); test_int(ctx.system, t); test_int(ctx.event, EcsOnAdd); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], inst); test_int(ctx.s[0][0], base); ecs_fini(world); } void Observer_on_set_w_tag(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); Probe ctx = {0}; ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }}, .events = {EcsOnSet}, .callback = Observer, .ctx = &ctx }); test_assert(t1 != 0); ecs_entity_t e = ecs_new_id(world); test_assert(e != 0); ecs_add(world, e, TagA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e); test_int(ctx.event, EcsOnAdd); test_int(ctx.event_id, TagA); ecs_fini(world); } void Observer_mixed_on_set_w_tag(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_COMPONENT(world, Position); Probe ctx = {0}; ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }, { ecs_id(Position) }}, .events = {EcsOnSet}, .callback = Observer, .ctx = &ctx }); test_assert(t1 != 0); ecs_entity_t e1 = ecs_new_id(world); test_assert(e1 != 0); ecs_add(world, e1, TagA); test_int(ctx.invoked, 0); ecs_add(world, e1, Position); test_int(ctx.invoked, 0); ecs_set(world, e1, Position, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e1); test_int(ctx.event, EcsOnSet); test_int(ctx.event_id, ecs_id(Position)); ctx = (Probe){0}; ecs_entity_t e2 = ecs_new_id(world); test_assert(e2 != 0); ecs_set(world, e2, Position, {10, 20}); test_int(ctx.invoked, 0); ecs_add(world, e2, TagA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e2); test_int(ctx.event, EcsOnAdd); test_int(ctx.event_id, TagA); ecs_fini(world); } void Observer_mixed_un_set_w_tag(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_COMPONENT(world, Position); Probe ctx = {0}; ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }, { ecs_id(Position) }}, .events = {EcsUnSet}, .callback = Observer, .ctx = &ctx }); test_assert(t1 != 0); ecs_entity_t e1 = ecs_new_id(world); ecs_add(world, e1, TagA); ecs_set(world, e1, Position, {10, 20}); test_int(ctx.invoked, 0); ecs_remove(world, e1, TagA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e1); test_int(ctx.event, EcsOnRemove); test_int(ctx.event_id, TagA); ctx = (Probe){0}; ecs_entity_t e2 = ecs_new_id(world); ecs_set(world, e2, Position, {10, 20}); ecs_add(world, e2, TagA); test_int(ctx.invoked, 0); ecs_remove(world, e2, Position); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e2); test_int(ctx.event, EcsUnSet); test_int(ctx.event_id, ecs_id(Position)); ecs_fini(world); } void Observer_match_base_w_id_at_offset(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); Probe ctx_1 = {0}; ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ ecs_id(Position) }}, .events = {EcsOnSet}, .callback = Observer, .ctx = &ctx_1 }); test_assert(t1 != 0); Probe ctx_2 = {0}; ecs_entity_t t2 = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ ecs_id(Velocity) }}, .events = {EcsOnSet}, .callback = Observer, .ctx = &ctx_2 }); test_assert(t2 != 0); ecs_entity_t base = ecs_new_w_id(world, EcsPrefab); ecs_set(world, base, Position, {10, 20}); ecs_set(world, base, Velocity, {1, 2}); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], inst); test_int(ctx_1.event, EcsOnSet); test_int(ctx_1.event_id, ecs_id(Position)); test_int(ctx_2.invoked, 1); test_int(ctx_2.count, 1); test_int(ctx_2.e[0], inst); test_int(ctx_2.event, EcsOnSet); test_int(ctx_2.event_id, ecs_id(Velocity)); ecs_fini(world); } static int run_invoked = 0; static int run_invoked_matched = 0; static void Run(ecs_iter_t *it) { if (ecs_observer_default_run_action(it)) { run_invoked_matched ++; } run_invoked ++; } static void Run_w_iter_next(ecs_iter_t *it) { run_invoked ++; test_assert(it != NULL); test_assert(it->next != NULL); test_assert(it->callback != NULL); while (ecs_iter_next(it)) { it->callback(it); } } void Observer_custom_run_action(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); Probe ctx = {0}; ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }}, .events = {EcsOnAdd}, .run = Run, .callback = Observer, .ctx = &ctx }); test_assert(t1 != 0); ecs_entity_t e = ecs_new(world, TagA); test_int(run_invoked, 1); test_int(run_invoked_matched, 1); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_custom_run_action_w_iter_next(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); Probe ctx = {0}; ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }}, .events = {EcsOnAdd}, .run = Run_w_iter_next, .callback = Observer, .ctx = &ctx }); test_assert(t1 != 0); ecs_entity_t e = ecs_new(world, TagA); test_int(run_invoked, 1); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_custom_run_action_2_terms(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }, { TagB }}, .events = {EcsOnAdd}, .run = Run, .callback = Observer, .ctx = &ctx }); test_assert(t1 != 0); ecs_entity_t e = ecs_new(world, TagA); test_int(run_invoked, 1); run_invoked = 0; ecs_add(world, e, TagB); test_int(run_invoked, 1); test_int(run_invoked_matched, 1); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_custom_run_action_w_iter_next_2_terms(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ TagA }, { TagB }}, .events = {EcsOnAdd}, .run = Run_w_iter_next, .callback = Observer, .ctx = &ctx }); test_assert(t1 != 0); ecs_entity_t e = ecs_new(world, TagA); test_int(run_invoked, 1); run_invoked = 0; ecs_add(world, e, TagB); test_int(run_invoked, 1); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_read_in_on_remove_after_add_other_w_not(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ECS_TAG(world, O); Probe ctx = {0}; ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { .id = ecs_pair(ecs_id(Position), O)}, { .id = Tag, .oper = EcsNot } }, .events = {EcsOnRemove}, .callback = Observer_w_1_value, .ctx = &ctx }); ecs_entity_t e = ecs_set_pair(world, 0, Position, O, { 10, 20 }); test_int(ctx.invoked, 0); ecs_add(world, e, Tag); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e); test_int(ctx.event, EcsOnRemove); ecs_fini(world); } void Observer_observer_w_short_notation(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Foo); Probe ctx = {0}; ecs_entity_t o = ecs_observer(world, { .filter.terms = {{ .id = Foo }}, .events = { EcsOnAdd }, .callback = Observer, .ctx = &ctx }); test_assert(o != 0); ecs_entity_t e = ecs_new(world, Foo); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_observer_w_filter_term(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_observer(world, { .filter.terms = {{ .id = TagA }, { .id = TagB, .src.flags = EcsFilter }}, .events = { EcsOnAdd }, .callback = Observer, .ctx = &ctx }); ecs_entity_t e = ecs_new_id(world); test_int(ctx.invoked, 0); ecs_add(world, e, TagB); test_int(ctx.invoked, 0); ecs_add(world, e, TagA); test_int(ctx.invoked, 1); ecs_remove(world, e, TagB); ecs_add(world, e, TagB); test_int(ctx.invoked, 1); ecs_clear(world, e); test_int(ctx.invoked, 1); ecs_add(world, e, TagA); test_int(ctx.invoked, 1); ecs_fini(world); } static int free_ctx_invoked = 0; static void free_ctx(void *ctx) { free_ctx_invoked ++; ecs_os_free(ctx); } static int observer_w_ctx_invoked = 0; static void Observer_w_ctx(ecs_iter_t *it) { test_assert(it->ctx != NULL); test_int(*(int*)it->ctx, 10); observer_w_ctx_invoked ++; } void Observer_multi_observer_w_ctx_free(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); int *ctx = ecs_os_malloc_t(int); *ctx = 10; ecs_entity_t o = ecs_observer(world, { .filter.terms = {{ .id = TagA }, { .id = TagB }}, .events = { EcsOnAdd }, .callback = Observer_w_ctx, .ctx = ctx, .ctx_free = free_ctx }); test_int(observer_w_ctx_invoked, 0); test_int(free_ctx_invoked, 0); ecs_entity_t e = ecs_new(world, TagA); test_int(observer_w_ctx_invoked, 0); test_int(free_ctx_invoked, 0); ecs_add(world, e, TagB); test_int(observer_w_ctx_invoked, 1); test_int(free_ctx_invoked, 0); ecs_delete(world, o); test_int(free_ctx_invoked, 1); ecs_fini(world); test_int(free_ctx_invoked, 1); } void Observer_propagate_after_on_delete_clear_action(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Rel); ecs_entity_t tgt = ecs_new_id(world); ecs_entity_t parent = ecs_new_id(world); ecs_add_pair(world, parent, Rel, tgt); Probe ctx = {0}; ecs_observer(world, { .filter.terms = {{ .id = ecs_pair(Rel, EcsWildcard), .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = { EcsOnRemove }, .callback = Observer, .ctx = &ctx }); ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); test_int(ctx.invoked, 0); ecs_delete(world, tgt); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], child); test_int(ctx.s[0][0], parent); test_int(ctx.event, EcsOnRemove); ecs_fini(world); } void Observer_on_add_after_batch_w_exclusive_adds(void) { ecs_world_t *world = ecs_mini(); ECS_ENTITY(world, Rel, Exclusive); ECS_TAG(world, TgtA); ECS_TAG(world, TgtB); Probe ctx = {0}; ecs_observer(world, { .filter.terms = {{ ecs_pair(Rel, EcsWildcard), .src.flags = EcsSelf }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); ecs_entity_t e = ecs_new_id(world); ecs_defer_begin(world); ecs_add_pair(world, e, Rel, TgtA); ecs_add_pair(world, e, Rel, TgtB); ecs_defer_end(world); test_assert(!ecs_has_pair(world, e, Rel, TgtA)); test_assert(ecs_has_pair(world, e, Rel, TgtB)); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][0], ecs_pair(Rel, TgtB)); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_propagate_match_relationship_w_self_up(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_observer(world, { .filter.terms = {{ TagB, .src.flags = EcsSelf|EcsUp }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); ecs_entity_t parent = ecs_new_id(world); ecs_add(world, parent, TagA); ecs_new_w_pair(world, EcsChildOf, parent); test_int(ctx.invoked, 0); ecs_add(world, parent, TagB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], parent); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][0], TagB); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_propagate_match_relationship_w_up(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_observer(world, { .filter.terms = {{ TagB, .src.flags = EcsUp }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); ecs_entity_t parent = ecs_new_id(world); ecs_add(world, parent, TagA); ecs_new_w_pair(world, EcsChildOf, parent); test_int(ctx.invoked, 0); ecs_add(world, parent, TagB); test_int(ctx.invoked, 0); ecs_fini(world); } void Observer_observer_w_2_fixed_src(void) { ecs_world_t *world = ecs_init(); ecs_entity_t Foo = ecs_new_id(world); ecs_entity_t Bar = ecs_new_id(world); ecs_entity_t e1 = ecs_new_id(world); ecs_entity_t e2 = ecs_new_id(world); Probe ctx = {0}; ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { // Comment out either of these and the callback succeeds. { Foo , .src.id = e1 }, { Bar, .src.id = e2 }, }, .events = { EcsOnAdd }, .callback = Observer, .ctx = &ctx }); ecs_add_id(world, e1, Foo); test_int(ctx.invoked, 0); ecs_add_id(world, e2, Bar); test_int(ctx.invoked, 1); ecs_fini(world); } void Observer_emit_for_recreated_id_after_remove_all(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, Rel); ECS_TAG(world, Tgt); ecs_entity_t e1 = ecs_new(world, TagA); ecs_add_pair(world, e1, Rel, Tgt); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { TagA }, { ecs_pair(Rel, Tgt) } }, .events = { EcsOnAdd }, .callback = Observer, .ctx = &ctx }); ecs_remove_all(world, ecs_pair(Rel, Tgt)); test_assert(ecs_id_is_valid(world, ecs_pair(Rel, Tgt))); test_assert(ecs_is_alive(world, e1)); test_int(0, ctx.invoked); ecs_entity_t e2 = ecs_new(world, TagA); ecs_add_pair(world, e2, Rel, Tgt); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e2); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], ecs_pair(Rel, Tgt)); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_emit_for_recreated_id_after_remove_all_wildcard(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, Rel); ECS_TAG(world, Tgt); ecs_entity_t e1 = ecs_new(world, TagA); ecs_add_pair(world, e1, Rel, Tgt); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { TagA }, { ecs_pair(Rel, Tgt) } }, .events = { EcsOnAdd }, .callback = Observer, .ctx = &ctx }); ecs_remove_all(world, ecs_pair(Rel, EcsWildcard)); test_assert(ecs_id_is_valid(world, ecs_pair(Rel, Tgt))); test_assert(ecs_is_alive(world, e1)); test_int(0, ctx.invoked); ecs_entity_t e2 = ecs_new(world, TagA); ecs_add_pair(world, e2, Rel, Tgt); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e2); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], ecs_pair(Rel, Tgt)); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_emit_for_recreated_id_after_delete_with(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, Rel); ECS_TAG(world, Tgt); ecs_entity_t e1 = ecs_new(world, TagA); ecs_add_pair(world, e1, Rel, Tgt); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { TagA }, { ecs_pair(Rel, Tgt) } }, .events = { EcsOnAdd }, .callback = Observer, .ctx = &ctx }); ecs_delete_with(world, ecs_pair(Rel, Tgt)); test_assert(ecs_id_is_valid(world, ecs_pair(Rel, Tgt))); test_assert(!ecs_is_alive(world, e1)); test_int(0, ctx.invoked); ecs_entity_t e2 = ecs_new(world, TagA); ecs_add_pair(world, e2, Rel, Tgt); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e2); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], ecs_pair(Rel, Tgt)); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_emit_for_recreated_id_after_delete_with_wildcard(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, Rel); ECS_TAG(world, Tgt); ecs_entity_t e1 = ecs_new(world, TagA); ecs_add_pair(world, e1, Rel, Tgt); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { TagA }, { ecs_pair(Rel, Tgt) } }, .events = { EcsOnAdd }, .callback = Observer, .ctx = &ctx }); ecs_delete_with(world, ecs_pair(Rel, EcsWildcard)); test_assert(ecs_id_is_valid(world, ecs_pair(Rel, Tgt))); test_assert(!ecs_is_alive(world, e1)); test_int(0, ctx.invoked); ecs_entity_t e2 = ecs_new(world, TagA); ecs_add_pair(world, e2, Rel, Tgt); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e2); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][0], TagA); test_int(ctx.c[0][1], ecs_pair(Rel, Tgt)); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_delete_observed_id(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, Rel); ECS_TAG(world, Tgt); ecs_entity_t e1 = ecs_new(world, TagA); ecs_add_pair(world, e1, Rel, Tgt); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { TagA }, { ecs_pair(Rel, Tgt) } }, .events = { EcsOnAdd }, .callback = Observer, .ctx = &ctx }); test_expect_abort(); ecs_delete(world, TagA); } void Observer_delete_observed_rel(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, Rel); ECS_TAG(world, Tgt); ecs_entity_t e1 = ecs_new(world, TagA); ecs_add_pair(world, e1, Rel, Tgt); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { TagA }, { ecs_pair(Rel, Tgt) } }, .events = { EcsOnAdd }, .callback = Observer, .ctx = &ctx }); test_expect_abort(); ecs_delete(world, Rel); } void Observer_delete_observed_tgt(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, Rel); ECS_TAG(world, Tgt); ecs_entity_t e1 = ecs_new(world, TagA); ecs_add_pair(world, e1, Rel, Tgt); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { TagA }, { ecs_pair(Rel, Tgt) } }, .events = { EcsOnAdd }, .callback = Observer, .ctx = &ctx }); test_expect_abort(); ecs_delete(world, Tgt); } static int pair_x = 0; static int32_t pair_column = 0; static void OnTagPair(ecs_iter_t *it) { test_int(it->count, 1); pair_column = it->columns[0]; probe_iter(it); } static void OnPair(ecs_iter_t *it) { test_int(it->count, 1); Position *p = ecs_field(it, Position, 1); pair_x = p->x; pair_column = it->columns[0]; probe_iter(it); } void Observer_on_add_2_pairs_w_uni_observer(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Rel); ECS_TAG(world, TgtA); ECS_TAG(world, TgtB); ECS_TAG(world, TgtC); ECS_TAG(world, Tag); ECS_OBSERVER(world, OnTagPair, EcsOnAdd, (Rel, *)); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e = ecs_new_id(world); ecs_add(world, e, Tag); ecs_add_pair(world, e, Rel, TgtA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnTagPair); test_int(ctx.term_count, 1); test_int(ctx.event_id, ecs_pair(Rel, TgtA)); test_int(pair_column, 2); ecs_os_zeromem(&ctx); ecs_add_pair(world, e, Rel, TgtB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnTagPair); test_int(ctx.term_count, 1); test_int(ctx.event_id, ecs_pair(Rel, TgtB)); test_int(pair_column, 3); ecs_os_zeromem(&ctx); ecs_add_pair(world, e, Rel, TgtC); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnTagPair); test_int(ctx.term_count, 1); test_int(ctx.event_id, ecs_pair(Rel, TgtC)); test_int(pair_column, 4); ecs_fini(world); } void Observer_on_add_2_pairs_w_multi_observer(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Rel); ECS_TAG(world, TgtA); ECS_TAG(world, TgtB); ECS_TAG(world, TgtC); ECS_TAG(world, Tag); ECS_OBSERVER(world, OnTagPair, EcsOnAdd, (Rel, *), Tag); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e = ecs_new_id(world); ecs_add(world, e, Tag); ecs_add_pair(world, e, Rel, TgtA); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnTagPair); test_int(ctx.term_count, 2); test_int(ctx.event_id, ecs_pair(Rel, TgtA)); test_int(pair_column, 2); ecs_os_zeromem(&ctx); ecs_add_pair(world, e, Rel, TgtB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnTagPair); test_int(ctx.term_count, 2); test_int(ctx.event_id, ecs_pair(Rel, TgtB)); test_int(pair_column, 3); ecs_os_zeromem(&ctx); ecs_add_pair(world, e, Rel, TgtC); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnTagPair); test_int(ctx.term_count, 2); test_int(ctx.event_id, ecs_pair(Rel, TgtC)); test_int(pair_column, 4); ecs_fini(world); } void Observer_on_set_2_pairs_w_uni_observer(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, TgtA); ECS_TAG(world, TgtB); ECS_TAG(world, TgtC); ECS_TAG(world, Tag); ECS_OBSERVER(world, OnPair, EcsOnSet, (Position, *)); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e = ecs_new_id(world); ecs_add(world, e, Tag); ecs_set_pair(world, e, Position, TgtA, {1}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnPair); test_int(ctx.term_count, 1); test_int(ctx.event_id, ecs_pair(ecs_id(Position), TgtA)); test_int(pair_column, 2); test_int(pair_x, 1); ecs_os_zeromem(&ctx); ecs_set_pair(world, e, Position, TgtB, {2}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnPair); test_int(ctx.term_count, 1); test_int(ctx.event_id, ecs_pair(ecs_id(Position), TgtB)); test_int(pair_column, 3); test_int(pair_x, 2); ecs_os_zeromem(&ctx); ecs_set_pair(world, e, Position, TgtC, {3}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnPair); test_int(ctx.term_count, 1); test_int(ctx.event_id, ecs_pair(ecs_id(Position), TgtC)); test_int(pair_column, 4); test_int(pair_x, 3); ecs_fini(world); } void Observer_on_set_2_pairs_w_multi_observer(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, TgtA); ECS_TAG(world, TgtB); ECS_TAG(world, TgtC); ECS_TAG(world, Tag); ECS_OBSERVER(world, OnPair, EcsOnSet, (Position, *), Tag); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); ecs_entity_t e = ecs_new_id(world); ecs_add(world, e, Tag); ecs_set_pair(world, e, Position, TgtA, {1}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnPair); test_int(ctx.term_count, 2); test_int(ctx.event_id, ecs_pair(ecs_id(Position), TgtA)); test_int(pair_column, 2); test_int(pair_x, 1); ecs_os_zeromem(&ctx); ecs_set_pair(world, e, Position, TgtB, {2}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnPair); test_int(ctx.term_count, 2); test_int(ctx.event_id, ecs_pair(ecs_id(Position), TgtB)); test_int(pair_column, 3); test_int(pair_x, 2); ecs_os_zeromem(&ctx); ecs_set_pair(world, e, Position, TgtC, {3}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, OnPair); test_int(ctx.term_count, 2); test_int(ctx.event_id, ecs_pair(ecs_id(Position), TgtC)); test_int(pair_column, 4); test_int(pair_x, 3); ecs_fini(world); } void Observer_on_remove_target_from_base_at_offset(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t R = ecs_new_id(world); ecs_entity_t T1 = ecs_new_id(world); ecs_entity_t T2 = ecs_new_id(world); ecs_entity_t C = ecs_new_id(world); Probe ctx = { 0 }; ecs_entity_t o = ecs_observer(world, { .filter.terms = { { .id = ecs_pair(R, EcsWildcard), .src.flags = EcsUp }, { .id = C }, }, .events = { EcsOnRemove }, .callback = Observer, .ctx = &ctx }); ecs_entity_t base = ecs_new_id(world); ecs_add_pair(world, base, R, T1); ecs_add_pair(world, base, R, T2); ecs_entity_t e = ecs_new_id(world); ecs_add_pair(world, e, EcsIsA, base); ecs_add_id(world, e, C); test_int(ctx.invoked, 0); ecs_delete(world, T2); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.term_count, 2); test_int(ctx.event_id, ecs_pair(R, T2)); test_int(ctx.event, EcsOnRemove); ecs_fini(world); } static void Observer_base_component(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); Position *p = ecs_field(it, Position, 1); test_assert(p != NULL); test_int(p->x, 30); test_int(p->y, 40); } void Observer_on_remove_target_component_from_base_at_offset(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ecs_entity_t T1 = ecs_new_id(world); ecs_entity_t T2 = ecs_new_id(world); ecs_entity_t C = ecs_new_id(world); Probe ctx = { 0 }; ecs_entity_t o = ecs_observer(world, { .filter.terms = { { .id = ecs_pair(ecs_id(Position), EcsWildcard), .src.flags = EcsUp }, { .id = C }, }, .events = { EcsOnRemove }, .callback = Observer_base_component, .ctx = &ctx }); ecs_entity_t base = ecs_new_id(world); ecs_set_pair(world, base, Position, T1, {10, 20}); ecs_set_pair(world, base, Position, T2, {30, 40}); ecs_entity_t e = ecs_new_id(world); ecs_add_pair(world, e, EcsIsA, base); ecs_add_id(world, e, C); test_int(ctx.invoked, 0); ecs_delete(world, T2); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.system, o); test_int(ctx.term_count, 2); test_int(ctx.e[0], e); test_int(ctx.s[0][0], base); test_int(ctx.s[0][1], 0); test_int(ctx.event_id, ecs_pair(ecs_id(Position), T2)); test_int(ctx.event, EcsOnRemove); ecs_delete(world, o); ecs_fini(world); } static void Observer_w_other_table(ecs_iter_t *it) { probe_system_w_ctx(it, it->ctx); test_assert(it->table != NULL); test_assert(it->other_table != NULL); } static void Observer_dummy(ecs_iter_t *it) {} void Observer_wildcard_propagate_w_other_table(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t tag = ecs_new_id(world); ecs_entity_t rel = ecs_new_id(world); ecs_entity_t tgt_1 = ecs_new_id(world); ecs_entity_t parent = ecs_new_w_id(world, tag); ecs_new_w_pair(world, EcsChildOf, parent); Probe ctx_parent = {0}; ecs_observer(world, { .filter.terms = {{ ecs_pair(rel, EcsWildcard) }}, .events = {EcsOnAdd}, .callback = Observer_dummy }); ecs_observer(world, { .filter.terms = {{ ecs_pair(rel, EcsWildcard) }}, .events = {EcsWildcard}, .callback = Observer_w_other_table, .ctx = &ctx_parent }); ecs_observer(world, { .filter.terms = {{ ecs_pair(rel, EcsWildcard), .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsWildcard}, .callback = Observer_dummy }); ecs_add_pair(world, parent, rel, tgt_1); test_int(ctx_parent.invoked, 1); ecs_fini(world); } void Observer_cache_test_1(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t tag = ecs_new_id(world); ecs_entity_t e1 = ecs_new_id(world); ecs_entity_t e2 = ecs_new_id(world); Probe ctx = {0}; ecs_observer(world, { .filter.terms = {{ tag, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = &ctx }); ecs_add_id(world, e2, ecs_pair(EcsChildOf, e1)); ecs_add_id(world, e1, tag); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e2); test_int(ctx.s[0][0], e1); test_int(ctx.c[0][0], tag); test_int(ctx.event, EcsOnAdd); ecs_os_zeromem(&ctx); ecs_entity_t e3 = ecs_new_id(world); ecs_add_id(world, e3, ecs_pair(EcsChildOf, e2)); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e3); test_int(ctx.s[0][0], e1); test_int(ctx.c[0][0], tag); test_int(ctx.event, EcsOnAdd); ecs_os_zeromem(&ctx); ecs_delete(world, e3); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e3); test_int(ctx.s[0][0], e1); test_int(ctx.c[0][0], tag); test_int(ctx.event, EcsOnRemove); ecs_os_zeromem(&ctx); ecs_remove_id(world, e1, tag); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e2); test_int(ctx.s[0][0], e1); test_int(ctx.c[0][0], tag); test_int(ctx.event, EcsOnRemove); ecs_os_zeromem(&ctx); e3 = ecs_new_id(world); ecs_add_id(world, e3, ecs_pair(EcsChildOf, e2)); test_int(ctx.invoked, 0); ecs_fini(world); } void Observer_cache_test_2(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t tag_1 = ecs_new_id(world); ecs_entity_t tag_2 = ecs_new_id(world); ecs_entity_t e1 = ecs_new_id(world); ecs_entity_t e2 = ecs_new_id(world); ecs_entity_t e3 = ecs_new_id(world); Probe ctx_1 = {0}; Probe ctx_2 = {0}; ecs_observer(world, { .filter.terms = {{ tag_1, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = &ctx_1 }); ecs_observer(world, { .filter.terms = {{ tag_2, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = &ctx_2 }); ecs_add_id(world, e1, tag_1); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add_id(world, e2, ecs_pair(EcsChildOf, e1)); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e2); test_int(ctx_1.s[0][0], e1); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnAdd); ecs_os_zeromem(&ctx_1); test_int(ctx_2.invoked, 0); ecs_add_id(world, e2, tag_2); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add_id(world, e3, ecs_pair(EcsChildOf, e2)); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e3); test_int(ctx_1.s[0][0], e1); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnAdd); ecs_os_zeromem(&ctx_1); test_int(ctx_2.invoked, 1); test_int(ctx_2.count, 1); test_int(ctx_2.e[0], e3); test_int(ctx_2.s[0][0], e2); test_int(ctx_2.c[0][0], tag_2); test_int(ctx_2.event, EcsOnAdd); ecs_os_zeromem(&ctx_2); ecs_fini(world); } void Observer_cache_test_3(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t tag_1 = ecs_new_id(world); ecs_entity_t tag_2 = ecs_new_id(world); ecs_entity_t e1 = ecs_new_id(world); ecs_entity_t e2 = ecs_new_id(world); ecs_entity_t e3 = ecs_new_id(world); Probe ctx_1 = {0}; Probe ctx_2 = {0}; ecs_observer(world, { .filter.terms = {{ tag_1, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = &ctx_1 }); ecs_observer(world, { .filter.terms = {{ tag_2, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = &ctx_2 }); ecs_add_id(world, e1, tag_1); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add_id(world, e2, ecs_pair(EcsChildOf, e1)); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e2); test_int(ctx_1.s[0][0], e1); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnAdd); ecs_os_zeromem(&ctx_1); test_int(ctx_2.invoked, 0); ecs_add_id(world, e2, tag_1); ecs_add_id(world, e2, tag_2); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add_id(world, e3, ecs_pair(EcsChildOf, e2)); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e3); test_int(ctx_1.s[0][0], e2); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnAdd); ecs_os_zeromem(&ctx_1); test_int(ctx_2.invoked, 1); test_int(ctx_2.count, 1); test_int(ctx_2.e[0], e3); test_int(ctx_2.s[0][0], e2); test_int(ctx_2.c[0][0], tag_2); test_int(ctx_2.event, EcsOnAdd); ecs_os_zeromem(&ctx_2); ecs_fini(world); } void Observer_cache_test_4(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t tag_0 = ecs_new_id(world); ecs_entity_t tag_1 = ecs_new_id(world); ecs_entity_t tag_2 = ecs_new_id(world); ecs_entity_t e0 = ecs_new_id(world); ecs_entity_t e1 = ecs_new_id(world); ecs_entity_t e2 = ecs_new_id(world); ecs_entity_t e3 = ecs_new_id(world); Probe *ctx_0 = ecs_os_calloc_t(Probe); Probe *ctx_1 = ecs_os_calloc_t(Probe); Probe *ctx_2 = ecs_os_calloc_t(Probe); ecs_observer(world, { .filter.terms = {{ tag_0, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = ctx_0 }); ecs_observer(world, { .filter.terms = {{ tag_1, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = ctx_1 }); ecs_observer(world, { .filter.terms = {{ tag_2, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = ctx_2 }); ecs_add_id(world, e0, tag_0); test_int(ctx_0->invoked, 0); test_int(ctx_1->invoked, 0); test_int(ctx_2->invoked, 0); ecs_add_id(world, e1, ecs_pair(EcsChildOf, e0)); test_int(ctx_0->invoked, 1); test_int(ctx_0->count, 1); test_int(ctx_0->e[0], e1); test_int(ctx_0->s[0][0], e0); test_int(ctx_0->c[0][0], tag_0); test_int(ctx_0->event, EcsOnAdd); ecs_os_zeromem(ctx_0); test_int(ctx_1->invoked, 0); test_int(ctx_2->invoked, 0); ecs_add_id(world, e1, tag_1); test_int(ctx_0->invoked, 0); test_int(ctx_1->invoked, 0); test_int(ctx_2->invoked, 0); ecs_add_id(world, e2, ecs_pair(EcsChildOf, e1)); test_int(ctx_0->invoked, 1); test_int(ctx_0->count, 1); test_int(ctx_0->e[0], e2); test_int(ctx_0->s[0][0], e0); test_int(ctx_0->c[0][0], tag_0); test_int(ctx_0->event, EcsOnAdd); ecs_os_zeromem(ctx_0); test_int(ctx_1->invoked, 1); test_int(ctx_1->count, 1); test_int(ctx_1->e[0], e2); test_int(ctx_1->s[0][0], e1); test_int(ctx_1->c[0][0], tag_1); test_int(ctx_1->event, EcsOnAdd); ecs_os_zeromem(ctx_1); test_int(ctx_2->invoked, 0); ecs_add_id(world, e2, tag_1); ecs_add_id(world, e2, tag_2); test_int(ctx_0->invoked, 0); test_int(ctx_1->invoked, 0); test_int(ctx_2->invoked, 0); ecs_remove_id(world, e0, tag_0); test_int(ctx_0->invoked, 2); ecs_os_zeromem(ctx_0); test_int(ctx_1->invoked, 0); test_int(ctx_2->invoked, 0); ecs_add_id(world, e3, ecs_pair(EcsChildOf, e2)); test_int(ctx_0->invoked, 0); test_int(ctx_1->invoked, 1); test_int(ctx_1->count, 1); test_int(ctx_1->e[0], e3); test_int(ctx_1->s[0][0], e2); test_int(ctx_1->c[0][0], tag_1); test_int(ctx_1->event, EcsOnAdd); ecs_os_zeromem(ctx_1); test_int(ctx_2->invoked, 1); test_int(ctx_2->count, 1); test_int(ctx_2->e[0], e3); test_int(ctx_2->s[0][0], e2); test_int(ctx_2->c[0][0], tag_2); test_int(ctx_2->event, EcsOnAdd); ecs_os_zeromem(ctx_2); ecs_fini(world); ecs_os_free(ctx_0); ecs_os_free(ctx_1); ecs_os_free(ctx_2); } void Observer_cache_test_5(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t tag_0 = ecs_new_id(world); ecs_entity_t tag_1 = ecs_new_id(world); ecs_entity_t tag_2 = ecs_new_id(world); ecs_entity_t e0 = ecs_new_id(world); ecs_entity_t e1 = ecs_new_id(world); ecs_entity_t e2 = ecs_new_id(world); ecs_entity_t e3 = ecs_new_id(world); ecs_entity_t e4 = ecs_new_id(world); Probe ctx_1 = {0}; Probe ctx_2 = {0}; ecs_observer(world, { .filter.terms = {{ tag_1, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = &ctx_1 }); ecs_observer(world, { .filter.terms = {{ tag_2, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = &ctx_2 }); ecs_add_id(world, e0, tag_0); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add_id(world, e1, ecs_pair(EcsChildOf, e0)); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add_id(world, e1, tag_1); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add_id(world, e2, ecs_pair(EcsChildOf, e1)); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e2); test_int(ctx_1.s[0][0], e1); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnAdd); ecs_os_zeromem(&ctx_1); test_int(ctx_2.invoked, 0); ecs_add_id(world, e2, tag_1); ecs_add_id(world, e2, tag_2); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_remove_id(world, e0, tag_0); test_int(ctx_1.invoked, 0); test_int(ctx_2.invoked, 0); ecs_add_id(world, e3, ecs_pair(EcsChildOf, e2)); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e3); test_int(ctx_1.s[0][0], e2); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnAdd); ecs_os_zeromem(&ctx_1); test_int(ctx_2.invoked, 1); test_int(ctx_2.count, 1); test_int(ctx_2.e[0], e3); test_int(ctx_2.s[0][0], e2); test_int(ctx_2.c[0][0], tag_2); test_int(ctx_2.event, EcsOnAdd); ecs_os_zeromem(&ctx_2); ecs_add_id(world, e4, ecs_pair(EcsChildOf, e1)); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e4); test_int(ctx_1.s[0][0], e1); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnAdd); ecs_os_zeromem(&ctx_1); test_int(ctx_2.invoked, 0); ecs_fini(world); } void Observer_cache_test_6(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t tag_1 = ecs_new_id(world); ecs_entity_t e1 = ecs_new_id(world); ecs_entity_t e2 = ecs_new_id(world); ecs_entity_t e3 = ecs_new_id(world); Probe ctx_1 = {0}; ecs_observer(world, { .filter.terms = {{ tag_1, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = &ctx_1 }); ecs_add_id(world, e1, tag_1); test_int(ctx_1.invoked, 0); ecs_add_id(world, e2, ecs_pair(EcsChildOf, e1)); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e2); test_int(ctx_1.s[0][0], e1); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnAdd); ecs_os_zeromem(&ctx_1); ecs_remove_id(world, e1, tag_1); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e2); test_int(ctx_1.s[0][0], e1); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnRemove); ecs_os_zeromem(&ctx_1); ecs_add_id(world, e3, ecs_pair(EcsChildOf, e1)); test_int(ctx_1.invoked, 0); ecs_fini(world); } void Observer_cache_test_7(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t tag_1 = ecs_new_id(world); ecs_entity_t tag_2 = ecs_new_id(world); ecs_entity_t e1 = ecs_new_id(world); ecs_entity_t e2 = ecs_new_id(world); ecs_entity_t e3 = ecs_new_id(world); Probe ctx_1 = {0}; ecs_observer(world, { .filter.terms = {{ tag_1, .src.flags = EcsUp, .src.trav = EcsChildOf }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = Observer, .ctx = &ctx_1 }); ecs_add_id(world, e1, tag_1); ecs_add_id(world, e1, tag_2); test_int(ctx_1.invoked, 0); ecs_add_id(world, e2, ecs_pair(EcsChildOf, e1)); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e2); test_int(ctx_1.s[0][0], e1); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnAdd); ecs_os_zeromem(&ctx_1); ecs_remove_id(world, e1, tag_1); test_int(ctx_1.invoked, 1); test_int(ctx_1.count, 1); test_int(ctx_1.e[0], e2); test_int(ctx_1.s[0][0], e1); test_int(ctx_1.c[0][0], tag_1); test_int(ctx_1.event, EcsOnRemove); ecs_os_zeromem(&ctx_1); ecs_add_id(world, e3, ecs_pair(EcsChildOf, e1)); test_int(ctx_1.invoked, 0); ecs_fini(world); } void Observer_cache_test_8(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, TagA); ECS_TAG(world, TagB); Probe ctx = {0}; ecs_observer(world, { .filter.terms = {{ TagB }}, .events = {EcsOnAdd}, .callback = Observer, .ctx = &ctx }); ecs_entity_t parent = ecs_new_id(world); ecs_add(world, parent, TagA); ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent); ecs_new_w_pair(world, EcsChildOf, child); test_int(ctx.invoked, 0); ecs_add(world, parent, TagB); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], parent); test_int(ctx.s[0][0], 0); test_int(ctx.c[0][0], TagB); test_int(ctx.event, EcsOnAdd); ecs_fini(world); } void Observer_cache_test_9(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); Probe ctx = {0}; ecs_observer(world, { .filter.terms = {{ ecs_id(Position), .src.flags = EcsUp }}, .events = { EcsOnRemove }, .callback = Observer, .ctx = &ctx }); ecs_entity_t base = ecs_new_w_id(world, EcsPrefab); ecs_set(world, base, Position, {10, 20}); ecs_entity_t base_2 = ecs_new_w_id(world, EcsPrefab); ecs_add_pair(world, base_2, EcsIsA, base); ecs_set(world, base_2, Position, {20, 30}); ecs_entity_t base_3 = ecs_new_w_id(world, EcsPrefab); ecs_add_pair(world, base_3, EcsIsA, base); ecs_set(world, base_3, Position, {40, 50}); ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base_2); ecs_add_pair(world, inst, EcsIsA, base_3); ecs_clear(world, base_2); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], inst); test_int(ctx.s[0][0], base_2); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.event, EcsOnRemove); ecs_os_zeromem(&ctx); ecs_clear(world, base_3); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], inst); test_int(ctx.s[0][0], base_3); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.event, EcsOnRemove); ecs_fini(world); } void Observer_cache_test_10(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t tag = ecs_new_id(world); ecs_entity_t ns = ecs_new_id(world); ecs_add_id(world, ns, tag); ecs_entity_t foo_parent = ecs_new_w_pair(world, EcsChildOf, ns); ecs_entity_t foo = ecs_new_w_pair(world, EcsChildOf, foo_parent); ecs_entity_t bar = ecs_new_id(world); ecs_entity_t parent = ecs_new_w_id(world, foo); ecs_entity_t e1 = ecs_new_id(world); ecs_add_id(world, e1, foo); ecs_add_pair(world, e1, EcsChildOf, parent); ecs_add_id(world, e1, bar); ecs_entity_t e2 = ecs_new_id(world); ecs_add_id(world, e2, foo); ecs_add_pair(world, e2, EcsChildOf, parent); ecs_add_id(world, e2, bar); ecs_entity_t c1 = ecs_new_id(world); ecs_add_id(world, c1, foo); ecs_add_pair(world, c1, EcsChildOf, e1); ecs_entity_t c2 = ecs_new_id(world); ecs_add_id(world, c2, foo); ecs_add_pair(world, c2, EcsChildOf, e2); ecs_delete(world, ns); test_assert(!ecs_is_alive(world, ns)); test_assert(!ecs_is_alive(world, foo)); test_assert(!ecs_is_alive(world, foo_parent)); test_assert(!ecs_has_id(world, parent, foo)); test_assert(!ecs_has_id(world, e1, foo)); test_assert(!ecs_has_id(world, e2, foo)); test_assert(!ecs_has_id(world, c1, foo)); test_assert(!ecs_has_id(world, c2, foo)); ecs_delete(world, parent); test_assert(!ecs_is_alive(world, parent)); test_assert(!ecs_is_alive(world, e1)); test_assert(!ecs_is_alive(world, e2)); test_assert(!ecs_is_alive(world, c1)); test_assert(!ecs_is_alive(world, c2)); ecs_fini(world); } void Observer_cache_test_11(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t ns = ecs_new_id(world); ecs_entity_t e1 = ecs_new_w_pair(world, EcsChildOf, ns); ecs_entity_t e2 = ecs_new_w_pair(world, EcsDependsOn, e1); ecs_add_pair(world, e2, EcsChildOf, e1); ecs_entity_t e3 = ecs_new_w_pair(world, EcsChildOf, ns); ecs_add_id(world, e3, EcsModule); ecs_entity_t e4 = ecs_new_w_pair(world, EcsChildOf, e3); ecs_add_pair(world, e4, EcsDependsOn, e2); ecs_add_id(world, e1, ecs_new_id(world)); ecs_run_aperiodic(world, 0); test_assert(true); // ensure cache revalidation didn't assert ecs_fini(world); } void Observer_cache_test_12(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t t0 = ecs_new_id(world); ecs_entity_t t1 = ecs_new_id(world); ecs_entity_t t2 = ecs_new_id(world); ecs_entity_t e0 = ecs_new_id(world); ecs_add_id(world, e0, t0); ecs_add_id(world, e0, t1); ecs_add_id(world, e0, t2); ecs_entity_t e1 = ecs_new_id(world); ecs_add_id(world, e1, t0); ecs_add_id(world, e1, t1); ecs_add_id(world, e1, t2); ecs_new_w_pair(world, EcsChildOf, e1); ecs_entity_t e3 = ecs_new_w_pair(world, EcsChildOf, e0); ecs_add_id(world, e3, t0); ecs_entity_t e4 = ecs_new_id(world); ecs_new_w_pair(world, EcsChildOf, e4); ecs_remove_all(world, t1); ecs_new_w_pair(world, EcsChildOf, e3); ecs_run_aperiodic(world, 0); ecs_remove_all(world, t0); test_assert(true); // ensure cache revalidation didn't assert ecs_fini(world); } void Observer_cache_test_13(void) { install_test_abort(); ecs_world_t *world = ecs_mini(); ecs_entity_t t0 = ecs_new_id(world); ecs_entity_t e0 = ecs_new_id(world); ecs_entity_t e1 = ecs_new_id(world); ecs_add_id(world, e1, t0); ecs_add_id(world, e1, ecs_pair(EcsChildOf, e0)); ecs_entity_t e2 = ecs_new_id(world); ecs_add_id(world, e2, t0); ecs_entity_t e3 = ecs_new_id(world); ecs_add_id(world, e3, ecs_pair(EcsChildOf, e2)); ecs_delete_with(world, t0); test_assert(!ecs_is_alive(world, e1)); test_assert(!ecs_is_alive(world, e2)); test_assert(!ecs_is_alive(world, e3)); ecs_entity_t e4 = ecs_new_id(world); test_expect_abort(); ecs_add_id(world, e4, ecs_pair(EcsChildOf, e2)); } void Observer_cache_test_14(void) { ecs_world_t *world = ecs_mini(); ECS_TAG(world, Tag); { ecs_entity_t e0 = ecs_new_id(world); ecs_add(world, e0, Tag); ecs_entity_t e1 = ecs_new_id(world); ecs_add_pair(world, e1, EcsChildOf, e0); ecs_entity_t e2 = ecs_new_id(world); ecs_add_pair(world, e2, EcsChildOf, e1); ecs_delete_with(world, Tag); test_assert(!ecs_is_alive(world, e0)); test_assert(!ecs_is_alive(world, e1)); test_assert(!ecs_is_alive(world, e2)); } { ecs_entity_t e0 = ecs_new_id(world); ecs_add(world, e0, Tag); ecs_entity_t e1 = ecs_new_id(world); ecs_add_pair(world, e1, EcsChildOf, e0); ecs_add(world, e1, Tag); ecs_entity_t e2 = ecs_new_id(world); ecs_add_pair(world, e2, EcsChildOf, e1); ecs_remove_all(world, Tag); test_assert(ecs_is_alive(world, e0)); test_assert(ecs_is_alive(world, e1)); test_assert(ecs_is_alive(world, e2)); test_assert(!ecs_has(world, e0, Tag)); } ecs_fini(world); } void Observer_cache_test_15(void) { ecs_world_t *world = ecs_mini(); ecs_entity_t r = ecs_new_id(world); ecs_entity_t e0 = ecs_new_id(world); ecs_entity_t e1 = ecs_new_id(world); ecs_add_pair(world, e1, r, e0); ecs_add_pair(world, e0, EcsChildOf, e1); ecs_run_aperiodic(world, 0); ecs_delete(world, e1); test_assert(!ecs_is_alive(world, e1)); test_assert(!ecs_is_alive(world, e0)); test_assert(ecs_is_alive(world, r)); ecs_fini(world); } static int Observer_a_invoked = 0; static int Observer_b_invoked = 0; static void Observer_a(ecs_iter_t *it) { Observer_a_invoked += it->count; } static void Observer_b(ecs_iter_t *it) { Observer_b_invoked += it->count; } void Observer_filter_observer_after_observer(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_TAG(world, Tag); ecs_observer(world, { .filter.terms = { { .id = ecs_id(Position) }, { .id = Tag } }, .callback = Observer_a, .events = { EcsOnAdd } }); ecs_observer(world, { .filter.terms = { { .id = ecs_id(Position), .inout = EcsInOutNone }, { .id = Tag, .inout = EcsInOutNone} }, .callback = Observer_b, .events = { EcsOnAdd } }); ecs_entity_t e = ecs_new(world, Tag); ecs_add(world, e, Position); test_int(Observer_a_invoked, 1); test_int(Observer_b_invoked, 1); ecs_fini(world); } void Observer_notify_after_defer_batched(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { .id = ecs_id(Position) }, { .id = ecs_id(Velocity) } }, .callback = Observer, .events = { EcsOnAdd }, .ctx = &ctx }); ecs_defer_begin(world); ecs_entity_t e1 = ecs_new_id(world); ecs_set(world, e1, Position, {10, 20}); test_int(ctx.invoked, 0); ecs_set(world, e1, Velocity, {1, 2}); ecs_defer_end(world); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e1); ecs_fini(world); } void Observer_notify_after_defer_batched_2_entities_in_table(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { .id = ecs_id(Position) }, { .id = ecs_id(Velocity) } }, .callback = Observer, .events = { EcsOnAdd }, .ctx = &ctx }); ecs_entity_t e1 = ecs_new_id(world); ecs_set(world, e1, Position, {10, 20}); test_int(ctx.invoked, 0); ecs_set(world, e1, Velocity, {1, 2}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e1); ecs_os_zeromem(&ctx); ecs_entity_t e2 = ecs_new_id(world); ecs_defer_begin(world); ecs_set(world, e2, Position, {30, 40}); ecs_set(world, e2, Velocity, {3, 4}); test_int(ctx.count, 0); ecs_defer_end(world); test_int(ctx.count, 1); test_int(ctx.e[0], e2); ecs_fini(world); } void Observer_notify_after_defer_batched_2_entities_in_table_w_tgt(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { .id = ecs_id(Position) }, { .id = ecs_id(Velocity) } }, .callback = Observer, .events = { EcsOnAdd }, .ctx = &ctx }); ecs_entity_t e1 = ecs_new_id(world); ecs_set(world, e1, Position, {10, 20}); test_int(ctx.invoked, 0); ecs_set(world, e1, Velocity, {1, 2}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e1); ecs_os_zeromem(&ctx); ecs_entity_t e2 = ecs_new_id(world); ecs_entity_t e3 = ecs_new_id(world); ecs_add_pair(world, e3, EcsChildOf, e2); ecs_defer_begin(world); ecs_set(world, e2, Position, {30, 40}); ecs_set(world, e2, Velocity, {3, 4}); test_int(ctx.count, 0); ecs_defer_end(world); test_int(ctx.count, 1); test_int(ctx.e[0], e2); ecs_fini(world); } void Observer_multi_observer_table_fill_w_singleton(void) { ecs_world_t* world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); Probe ctx = {0}; ecs_observer(world, { .filter.terms = { { .id = ecs_id(Position), .src.flags = EcsIsEntity, .src.id = ecs_id(Position) }, { .id = ecs_id(Velocity) }, }, .filter.flags = EcsFilterNoData, .callback = Observer, .events = { EcsOnTableFill }, .ctx = &ctx }); ecs_singleton_add(world, Position); test_int(ctx.invoked, 0); ecs_entity_t e = ecs_new_id(world); ecs_add(world, e, Velocity); test_int(ctx.invoked, 0); ecs_run_aperiodic(world, 0); test_int(ctx.invoked, 1); ecs_fini(world); } ECS_COMPONENT_DECLARE(Velocity); static void AddVelocity(ecs_iter_t *it) { for (int i = 0; i < it->count; i ++) { ecs_add(it->world, it->entities[i], Velocity); test_assert(!ecs_has(it->world, it->entities[i], Velocity)); } } void Observer_add_in_yield_existing(void) { ecs_world_t* world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT_DEFINE(world, Velocity); ecs_entity_t e1 = ecs_new(world, Position); ecs_entity_t e2 = ecs_new(world, Position); ecs_entity_t e3 = ecs_new(world, Position); ecs_observer(world, { .filter.terms = { { .id = ecs_id(Position) }, }, .callback = AddVelocity, .events = { EcsOnAdd }, .yield_existing = true, }); test_assert(ecs_has(world, e1, Position)); test_assert(ecs_has(world, e1, Velocity)); test_assert(ecs_has(world, e2, Position)); test_assert(ecs_has(world, e2, Velocity)); test_assert(ecs_has(world, e3, Position)); test_assert(ecs_has(world, e3, Velocity)); ecs_fini(world); } void Observer_add_in_yield_existing_multi(void) { ecs_world_t* world = ecs_mini(); ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Mass); ECS_COMPONENT_DEFINE(world, Velocity); ecs_entity_t e1 = ecs_new(world, Position); ecs_entity_t e2 = ecs_new(world, Position); ecs_entity_t e3 = ecs_new(world, Position); ecs_add(world, e1, Mass); ecs_add(world, e2, Mass); ecs_add(world, e3, Mass); ecs_observer(world, { .filter.terms = { { .id = ecs_id(Position) }, { .id = ecs_id(Mass) }, }, .callback = AddVelocity, .events = { EcsOnAdd }, .yield_existing = true, }); test_assert(ecs_has(world, e1, Position)); test_assert(ecs_has(world, e1, Mass)); test_assert(ecs_has(world, e1, Velocity)); test_assert(ecs_has(world, e2, Position)); test_assert(ecs_has(world, e2, Mass)); test_assert(ecs_has(world, e2, Velocity)); test_assert(ecs_has(world, e3, Position)); test_assert(ecs_has(world, e3, Mass)); test_assert(ecs_has(world, e3, Velocity)); ecs_fini(world); } void Observer_emit_for_parent_w_prefab_child_and_instance(void) { ecs_world_t* ecs = ecs_init(); ECS_COMPONENT(ecs, Position); Probe ctx = {0}; ecs_observer(ecs, { .filter.terms = { { .id = ecs_id(Position) } }, .events = { EcsOnSet }, .callback = Observer, .ctx = &ctx }); ecs_entity_t e = ecs_new_id(ecs); ecs_entity_t p = ecs_new_w_pair(ecs, EcsChildOf, e); ecs_entity_t child_1 = ecs_new_w_pair(ecs, EcsChildOf, e); ecs_add_pair(ecs, child_1, EcsIsA, p); test_int(ctx.invoked, 0); ecs_set(ecs, e, Position, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); test_int(ctx.e[0], e); test_int(ctx.c[0][0], ecs_id(Position)); test_int(ctx.event, EcsOnSet); ecs_fini(ecs); }