Skip to content

Commit 9650b11

Browse files
[observers] Fix issues with variables in triggered term of observer (#2023)
1 parent 5a26402 commit 9650b11

File tree

8 files changed

+424
-10
lines changed

8 files changed

+424
-10
lines changed

distr/flecs.c

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16819,6 +16819,38 @@ void flecs_default_uni_observer_run_callback(ecs_iter_t *it) {
1681916819
o->callback(it);
1682016820
}
1682116821

16822+
static
16823+
bool flecs_observer_query_has_range(
16824+
const ecs_query_t *query,
16825+
ecs_table_range_t *range,
16826+
const ecs_term_t *term,
16827+
ecs_id_t event_id,
16828+
ecs_iter_t *it)
16829+
{
16830+
bool first_var = (term->first.id & EcsIsVariable) && term->first.name;
16831+
bool second_var = (term->second.id & EcsIsVariable) && term->second.name;
16832+
if (!first_var && !second_var) {
16833+
return ecs_query_has_range(query, range, it);
16834+
}
16835+
16836+
*it = ecs_query_iter(query->world, query);
16837+
ecs_iter_set_var_as_range(it, 0, range);
16838+
if (first_var) {
16839+
ecs_iter_set_var(it, ecs_query_find_var(query, term->first.name),
16840+
ECS_IS_PAIR(event_id) ? ECS_PAIR_FIRST(event_id) : event_id);
16841+
}
16842+
if (second_var) {
16843+
if (!ECS_IS_PAIR(event_id)) {
16844+
ecs_iter_fini(it);
16845+
return false;
16846+
}
16847+
ecs_iter_set_var(it, ecs_query_find_var(query, term->second.name),
16848+
ECS_PAIR_SECOND(event_id));
16849+
}
16850+
16851+
return ecs_query_next(it);
16852+
}
16853+
1682216854
static
1682316855
void flecs_observer_invoke(
1682416856
ecs_observer_t *o,
@@ -17018,14 +17050,18 @@ void flecs_multi_observer_invoke(
1701817050

1701917051
bool match;
1702017052
if (is_not) {
17021-
match = ecs_query_has_table(o->query, table, &user_it);
17053+
ecs_table_range_t range = { .table = table };
17054+
match = flecs_observer_query_has_range(
17055+
o->query, &range, term, it->event_id, &user_it);
1702217056
if (match) {
1702317057
/* The target table matches but the entity hasn't moved to it yet.
1702417058
* Now match the not_query, which will populate the iterator with
1702517059
* data from the table the entity is still stored in. */
1702617060
user_it.flags |= EcsIterSkip; /* Prevent change detection on fini */
1702717061
ecs_iter_fini(&user_it);
17028-
match = ecs_query_has_table(impl->not_query, prev_table, &user_it);
17062+
ecs_table_range_t prev_range = { .table = prev_table };
17063+
match = flecs_observer_query_has_range(
17064+
impl->not_query, &prev_range, term, it->event_id, &user_it);
1702917065

1703017066
/* A not query replaces Not terms with Optional terms, so if the
1703117067
* regular query matches, the not_query should also match. */
@@ -17038,15 +17074,19 @@ void flecs_multi_observer_invoke(
1703817074
.count = it->count
1703917075
};
1704017076

17041-
match = ecs_query_has_range(o->query, &range, &user_it);
17077+
match = flecs_observer_query_has_range(
17078+
o->query, &range, term, it->event_id, &user_it);
1704217079
}
1704317080

1704417081
if (match) {
1704517082
/* Monitor observers only invoke when the query matches for the first
1704617083
* time with an entity */
1704717084
if (impl->flags & EcsObserverIsMonitor) {
1704817085
ecs_iter_t table_it;
17049-
if (ecs_query_has_table(o->query, prev_table, &table_it)) {
17086+
ecs_table_range_t prev_range = { .table = prev_table };
17087+
if (flecs_observer_query_has_range(
17088+
o->query, &prev_range, term, it->event_id, &table_it))
17089+
{
1705017090
/* Prevent change detection on fini */
1705117091
user_it.flags |= EcsIterSkip;
1705217092
table_it.flags |= EcsIterSkip;

src/observer.c

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,38 @@ void flecs_default_uni_observer_run_callback(ecs_iter_t *it) {
360360
o->callback(it);
361361
}
362362

363+
static
364+
bool flecs_observer_query_has_range(
365+
const ecs_query_t *query,
366+
ecs_table_range_t *range,
367+
const ecs_term_t *term,
368+
ecs_id_t event_id,
369+
ecs_iter_t *it)
370+
{
371+
bool first_var = (term->first.id & EcsIsVariable) && term->first.name;
372+
bool second_var = (term->second.id & EcsIsVariable) && term->second.name;
373+
if (!first_var && !second_var) {
374+
return ecs_query_has_range(query, range, it);
375+
}
376+
377+
*it = ecs_query_iter(query->world, query);
378+
ecs_iter_set_var_as_range(it, 0, range);
379+
if (first_var) {
380+
ecs_iter_set_var(it, ecs_query_find_var(query, term->first.name),
381+
ECS_IS_PAIR(event_id) ? ECS_PAIR_FIRST(event_id) : event_id);
382+
}
383+
if (second_var) {
384+
if (!ECS_IS_PAIR(event_id)) {
385+
ecs_iter_fini(it);
386+
return false;
387+
}
388+
ecs_iter_set_var(it, ecs_query_find_var(query, term->second.name),
389+
ECS_PAIR_SECOND(event_id));
390+
}
391+
392+
return ecs_query_next(it);
393+
}
394+
363395
static
364396
void flecs_observer_invoke(
365397
ecs_observer_t *o,
@@ -559,14 +591,18 @@ void flecs_multi_observer_invoke(
559591

560592
bool match;
561593
if (is_not) {
562-
match = ecs_query_has_table(o->query, table, &user_it);
594+
ecs_table_range_t range = { .table = table };
595+
match = flecs_observer_query_has_range(
596+
o->query, &range, term, it->event_id, &user_it);
563597
if (match) {
564598
/* The target table matches but the entity hasn't moved to it yet.
565599
* Now match the not_query, which will populate the iterator with
566600
* data from the table the entity is still stored in. */
567601
user_it.flags |= EcsIterSkip; /* Prevent change detection on fini */
568602
ecs_iter_fini(&user_it);
569-
match = ecs_query_has_table(impl->not_query, prev_table, &user_it);
603+
ecs_table_range_t prev_range = { .table = prev_table };
604+
match = flecs_observer_query_has_range(
605+
impl->not_query, &prev_range, term, it->event_id, &user_it);
570606

571607
/* A not query replaces Not terms with Optional terms, so if the
572608
* regular query matches, the not_query should also match. */
@@ -579,15 +615,19 @@ void flecs_multi_observer_invoke(
579615
.count = it->count
580616
};
581617

582-
match = ecs_query_has_range(o->query, &range, &user_it);
618+
match = flecs_observer_query_has_range(
619+
o->query, &range, term, it->event_id, &user_it);
583620
}
584621

585622
if (match) {
586623
/* Monitor observers only invoke when the query matches for the first
587624
* time with an entity */
588625
if (impl->flags & EcsObserverIsMonitor) {
589626
ecs_iter_t table_it;
590-
if (ecs_query_has_table(o->query, prev_table, &table_it)) {
627+
ecs_table_range_t prev_range = { .table = prev_table };
628+
if (flecs_observer_query_has_range(
629+
o->query, &prev_range, term, it->event_id, &table_it))
630+
{
591631
/* Prevent change detection on fini */
592632
user_it.flags |= EcsIterSkip;
593633
table_it.flags |= EcsIterSkip;

test/core/project.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2123,6 +2123,10 @@
21232123
"propagate_add_childof_of_base",
21242124
"propagate_remove_childof_of_base",
21252125
"emit_for_parent_w_prefab_child_and_instance",
2126+
"query_eval_w_component_that_triggered_observer",
2127+
"query_eval_w_pair_first_var_that_triggered_observer",
2128+
"query_eval_w_pair_second_var_that_triggered_observer",
2129+
"query_eval_w_pair_both_vars_that_triggered_observer",
21262130
"observer_w_2_fixed_src",
21272131
"emit_for_recreated_id_after_remove_all",
21282132
"emit_for_recreated_id_after_remove_all_wildcard",

test/core/src/Observer.c

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,28 @@ void Dummy(ecs_iter_t *it) {
127127
dummy_called = true;
128128
}
129129

130+
typedef struct ObserverEventIdCtx {
131+
ecs_entity_t entry_event;
132+
ecs_id_t expected_id;
133+
ecs_id_t next_id;
134+
int32_t invoked;
135+
} ObserverEventIdCtx;
136+
137+
static
138+
void ObserverOnEventId(ecs_iter_t *it) {
139+
ObserverEventIdCtx *ctx = it->ctx;
140+
test_int(it->count, 1);
141+
test_int(it->ids[0], ctx->expected_id);
142+
if (!ctx->invoked ++) {
143+
ecs_add_id(it->world, it->entities[0], ctx->next_id);
144+
ecs_enqueue(it->world, &(ecs_event_desc_t){
145+
.event = ctx->entry_event,
146+
.ids = &(ecs_type_t){ .count = 1, .array = (ecs_id_t[]){ ctx->next_id } },
147+
.entity = it->entities[0]
148+
});
149+
}
150+
}
151+
130152
void Observer_on_add_before_edge(void) {
131153
ecs_world_t *world = ecs_mini();
132154

@@ -6059,6 +6081,150 @@ void Observer_emit_for_parent_w_prefab_child_and_instance(void) {
60596081
ecs_fini(world);
60606082
}
60616083

6084+
void Observer_query_eval_w_component_that_triggered_observer(void) {
6085+
ecs_world_t *world = ecs_init();
6086+
6087+
ecs_entity_t entry_event = ecs_new(world);
6088+
ecs_entity_t sequence_shared = ecs_entity(world, { .name = "SequenceShared" });
6089+
ecs_add_id(world, sequence_shared, EcsTrait);
6090+
ecs_entity_t sequence = ecs_new_w_id(world, sequence_shared);
6091+
ecs_entity_t child = ecs_new(world);
6092+
6093+
ObserverEventIdCtx ctx = {
6094+
.entry_event = entry_event,
6095+
.expected_id = sequence,
6096+
.next_id = child
6097+
};
6098+
6099+
ecs_observer(world, {
6100+
.query.expr = "$Sequence, SequenceShared($Sequence)",
6101+
.events = { entry_event },
6102+
.callback = ObserverOnEventId,
6103+
.ctx = &ctx
6104+
});
6105+
6106+
ecs_enqueue(world, &(ecs_event_desc_t){
6107+
.event = entry_event,
6108+
.ids = &(ecs_type_t){ .count = 1, .array = (ecs_id_t[]){ sequence } },
6109+
.entity = ecs_new_w_id(world, sequence)
6110+
});
6111+
6112+
test_int(ctx.invoked, 1);
6113+
6114+
ecs_fini(world);
6115+
}
6116+
6117+
void Observer_query_eval_w_pair_first_var_that_triggered_observer(void) {
6118+
ecs_world_t *world = ecs_init();
6119+
6120+
ecs_entity_t entry_event = ecs_new(world);
6121+
ecs_entity_t rel_tag = ecs_entity(world, { .name = "RelTag" });
6122+
ecs_entity_t rel = ecs_entity(world, { .name = "MatchRel" });
6123+
ecs_entity_t tgt = ecs_entity(world, { .name = "MatchTgt" });
6124+
ecs_entity_t other_rel = ecs_entity(world, { .name = "OtherRel" });
6125+
ecs_add_id(world, rel, rel_tag);
6126+
6127+
ObserverEventIdCtx ctx = {
6128+
.entry_event = entry_event,
6129+
.expected_id = ecs_pair(rel, tgt),
6130+
.next_id = ecs_pair(other_rel, tgt)
6131+
};
6132+
6133+
ecs_observer(world, {
6134+
.query.expr = "($Rel, MatchTgt), RelTag($Rel)",
6135+
.events = { entry_event },
6136+
.callback = ObserverOnEventId,
6137+
.ctx = &ctx
6138+
});
6139+
6140+
ecs_entity_t e = ecs_new(world);
6141+
ecs_add_pair(world, e, rel, tgt);
6142+
ecs_enqueue(world, &(ecs_event_desc_t){
6143+
.event = entry_event,
6144+
.ids = &(ecs_type_t){ .count = 1, .array = (ecs_id_t[]){ ctx.expected_id } },
6145+
.entity = e
6146+
});
6147+
6148+
test_int(ctx.invoked, 1);
6149+
6150+
ecs_fini(world);
6151+
}
6152+
6153+
void Observer_query_eval_w_pair_second_var_that_triggered_observer(void) {
6154+
ecs_world_t *world = ecs_init();
6155+
6156+
ecs_entity_t entry_event = ecs_new(world);
6157+
ecs_entity_t tgt_tag = ecs_entity(world, { .name = "TgtTag" });
6158+
ecs_entity_t rel = ecs_entity(world, { .name = "MatchRel" });
6159+
ecs_entity_t tgt = ecs_entity(world, { .name = "MatchTgt" });
6160+
ecs_entity_t other_tgt = ecs_entity(world, { .name = "OtherTgt" });
6161+
ecs_add_id(world, tgt, tgt_tag);
6162+
6163+
ObserverEventIdCtx ctx = {
6164+
.entry_event = entry_event,
6165+
.expected_id = ecs_pair(rel, tgt),
6166+
.next_id = ecs_pair(rel, other_tgt)
6167+
};
6168+
6169+
ecs_observer(world, {
6170+
.query.expr = "(MatchRel, $Tgt), TgtTag($Tgt)",
6171+
.events = { entry_event },
6172+
.callback = ObserverOnEventId,
6173+
.ctx = &ctx
6174+
});
6175+
6176+
ecs_entity_t e = ecs_new(world);
6177+
ecs_add_pair(world, e, rel, tgt);
6178+
ecs_enqueue(world, &(ecs_event_desc_t){
6179+
.event = entry_event,
6180+
.ids = &(ecs_type_t){ .count = 1, .array = (ecs_id_t[]){ ctx.expected_id } },
6181+
.entity = e
6182+
});
6183+
6184+
test_int(ctx.invoked, 1);
6185+
6186+
ecs_fini(world);
6187+
}
6188+
6189+
void Observer_query_eval_w_pair_both_vars_that_triggered_observer(void) {
6190+
ecs_world_t *world = ecs_init();
6191+
6192+
ecs_entity_t entry_event = ecs_new(world);
6193+
ecs_entity_t rel_tag = ecs_entity(world, { .name = "RelTag" });
6194+
ecs_entity_t tgt_tag = ecs_entity(world, { .name = "TgtTag" });
6195+
ecs_entity_t rel = ecs_entity(world, { .name = "MatchRel" });
6196+
ecs_entity_t tgt = ecs_entity(world, { .name = "MatchTgt" });
6197+
ecs_entity_t other_rel = ecs_entity(world, { .name = "OtherRel" });
6198+
ecs_entity_t other_tgt = ecs_entity(world, { .name = "OtherTgt" });
6199+
ecs_add_id(world, rel, rel_tag);
6200+
ecs_add_id(world, tgt, tgt_tag);
6201+
6202+
ObserverEventIdCtx ctx = {
6203+
.entry_event = entry_event,
6204+
.expected_id = ecs_pair(rel, tgt),
6205+
.next_id = ecs_pair(other_rel, other_tgt)
6206+
};
6207+
6208+
ecs_observer(world, {
6209+
.query.expr = "($Rel, $Tgt), RelTag($Rel), TgtTag($Tgt)",
6210+
.events = { entry_event },
6211+
.callback = ObserverOnEventId,
6212+
.ctx = &ctx
6213+
});
6214+
6215+
ecs_entity_t e = ecs_new(world);
6216+
ecs_add_pair(world, e, rel, tgt);
6217+
ecs_enqueue(world, &(ecs_event_desc_t){
6218+
.event = entry_event,
6219+
.ids = &(ecs_type_t){ .count = 1, .array = (ecs_id_t[]){ ctx.expected_id } },
6220+
.entity = e
6221+
});
6222+
6223+
test_int(ctx.invoked, 1);
6224+
6225+
ecs_fini(world);
6226+
}
6227+
60626228
void Observer_observer_w_2_fixed_src(void) {
60636229
ecs_world_t *world = ecs_init();
60646230

test/core/src/main.c

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2056,6 +2056,10 @@ void Observer_propagate_remove_isa_of_parent(void);
20562056
void Observer_propagate_add_childof_of_base(void);
20572057
void Observer_propagate_remove_childof_of_base(void);
20582058
void Observer_emit_for_parent_w_prefab_child_and_instance(void);
2059+
void Observer_query_eval_w_component_that_triggered_observer(void);
2060+
void Observer_query_eval_w_pair_first_var_that_triggered_observer(void);
2061+
void Observer_query_eval_w_pair_second_var_that_triggered_observer(void);
2062+
void Observer_query_eval_w_pair_both_vars_that_triggered_observer(void);
20592063
void Observer_observer_w_2_fixed_src(void);
20602064
void Observer_emit_for_recreated_id_after_remove_all(void);
20612065
void Observer_emit_for_recreated_id_after_remove_all_wildcard(void);
@@ -11247,6 +11251,22 @@ bake_test_case Observer_testcases[] = {
1124711251
"emit_for_parent_w_prefab_child_and_instance",
1124811252
Observer_emit_for_parent_w_prefab_child_and_instance
1124911253
},
11254+
{
11255+
"query_eval_w_component_that_triggered_observer",
11256+
Observer_query_eval_w_component_that_triggered_observer
11257+
},
11258+
{
11259+
"query_eval_w_pair_first_var_that_triggered_observer",
11260+
Observer_query_eval_w_pair_first_var_that_triggered_observer
11261+
},
11262+
{
11263+
"query_eval_w_pair_second_var_that_triggered_observer",
11264+
Observer_query_eval_w_pair_second_var_that_triggered_observer
11265+
},
11266+
{
11267+
"query_eval_w_pair_both_vars_that_triggered_observer",
11268+
Observer_query_eval_w_pair_both_vars_that_triggered_observer
11269+
},
1125011270
{
1125111271
"observer_w_2_fixed_src",
1125211272
Observer_observer_w_2_fixed_src
@@ -16054,7 +16074,7 @@ static bake_test_suite suites[] = {
1605416074
"Observer",
1605516075
NULL,
1605616076
NULL,
16057-
328,
16077+
332,
1605816078
Observer_testcases
1605916079
},
1606016080
{

0 commit comments

Comments
 (0)