Skip to content

Commit 2900fb1

Browse files
committed
Fix issues with constrained variables and ChildOf terms
1 parent 4d485cf commit 2900fb1

File tree

10 files changed

+5200
-43
lines changed

10 files changed

+5200
-43
lines changed

distr/flecs.c

Lines changed: 113 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15638,7 +15638,7 @@ void flecs_emit(
1563815638
}
1563915639

1564015640
/* Forward events for Parent component as ChildOf pairs. */
15641-
if (id == ecs_id(EcsParent)) {
15641+
if (id == ecs_id(EcsParent) && !table_event) {
1564215642
ecs_event_desc_t pdesc = *desc;
1564315643

1564415644
pdesc.event = event;
@@ -16111,8 +16111,11 @@ void flecs_uni_observer_register(
1611116111
}
1611216112

1611316113
if (term && (term->trav == EcsChildOf)) {
16114-
flecs_register_observer_for_id(world, observable, o,
16115-
offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent));
16114+
ecs_assert(o->query != NULL, ECS_INTERNAL_ERROR, NULL);
16115+
if (o->query->flags & EcsQueryTableOnly) {
16116+
flecs_register_observer_for_id(world, observable, o,
16117+
offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent));
16118+
}
1611616119
}
1611716120
}
1611816121

@@ -16194,8 +16197,10 @@ void flecs_unregister_observer(
1619416197
}
1619516198

1619616199
if (term && (term->trav == EcsChildOf)) {
16197-
flecs_unregister_observer_for_id(world, observable, o,
16198-
offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent));
16200+
if (o->query->flags & EcsQueryTableOnly) {
16201+
flecs_unregister_observer_for_id(world, observable, o,
16202+
offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent));
16203+
}
1619916204
}
1620016205
}
1620116206

@@ -16946,7 +16951,7 @@ ecs_observer_t* flecs_observer_init(
1694616951
/* Only do optimization when not in sanitized mode. This ensures that the
1694716952
* behavior is consistent between observers with and without queries, as
1694816953
* both paths will be exercised in unit tests. */
16949-
// #ifndef FLECS_SANITIZE
16954+
#ifndef FLECS_SANITIZE
1695016955
/* Temporary arrays for dummy query */
1695116956
ecs_term_t terms[FLECS_TERM_COUNT_MAX] = {0};
1695216957
ecs_size_t sizes[FLECS_TERM_COUNT_MAX] = {0};
@@ -16968,8 +16973,10 @@ ecs_observer_t* flecs_observer_init(
1696816973
(dummy_query.flags & EcsQueryMatchOnlySelf) &&
1696916974
!dummy_query.row_fields;
1697016975
if (trivial_observer) {
16971-
dummy_query.flags |= desc->query.flags;
16972-
query = &dummy_query;
16976+
if (ECS_PAIR_FIRST(dummy_query.terms[0].id) != EcsChildOf) {
16977+
dummy_query.flags |= desc->query.flags;
16978+
query = &dummy_query;
16979+
}
1697316980
} else {
1697416981
/* We're going to create an actual query, so undo the keep_alive
1697516982
* increment of the dummy_query. */
@@ -16981,7 +16988,7 @@ ecs_observer_t* flecs_observer_init(
1698116988
}
1698216989
}
1698316990
}
16984-
// #endif
16991+
#endif
1698516992

1698616993
/* Create query */
1698716994
if (!query) {
@@ -74121,6 +74128,7 @@ ecs_query_cache_t* flecs_query_cache_init(
7412174128
ecs_os_memcpy_n(observer_desc.query.terms, q->terms,
7412274129
ecs_term_t, q->term_count);
7412374130
observer_desc.query.expr = NULL; /* Already parsed */
74131+
observer_desc.query.flags |= EcsQueryTableOnly;
7412474132

7412574133
result->observer = flecs_observer_init(world, entity, &observer_desc);
7412674134
if (!result->observer) {
@@ -82979,6 +82987,15 @@ bool flecs_query_trav(
8297982987
*/
8298082988

8298182989

82990+
static
82991+
const EcsParent* flecs_query_tree_get_parents(
82992+
ecs_table_range_t range)
82993+
{
82994+
int32_t parent_column = range.table->component_map[ecs_id(EcsParent)];
82995+
ecs_assert(parent_column != -1, ECS_INTERNAL_ERROR, NULL);
82996+
return ecs_table_get_column(range.table, parent_column - 1, range.offset);
82997+
}
82998+
8298282999
static
8298383000
bool flecs_query_tree_select_tgt(
8298483001
const ecs_query_op_t *op,
@@ -83299,10 +83316,7 @@ bool flecs_query_tree_with(
8329983316
}
8330083317

8330183318
/* Non-fragmenting childof */
83302-
int32_t parent_column = range.table->component_map[ecs_id(EcsParent)];
83303-
ecs_assert(parent_column != -1, ECS_INTERNAL_ERROR, NULL);
83304-
EcsParent *parents = ecs_table_get_column(
83305-
range.table, parent_column - 1, range.offset);
83319+
const EcsParent *parents = flecs_query_tree_get_parents(range);
8330683320

8330783321
/* Evaluating a single entity */
8330883322
if (range.count == 1) {
@@ -83413,7 +83427,8 @@ bool flecs_query_tree_with_pre(
8341383427
return false;
8341483428
}
8341583429

83416-
bool flecs_query_children(
83430+
static
83431+
bool flecs_query_children_select(
8341783432
const ecs_query_op_t *op,
8341883433
bool redo,
8341983434
const ecs_query_run_ctx_t *ctx)
@@ -83456,6 +83471,72 @@ bool flecs_query_children(
8345683471
return true;
8345783472
}
8345883473

83474+
static
83475+
bool flecs_query_children_with(
83476+
const ecs_query_op_t *op,
83477+
bool redo,
83478+
const ecs_query_run_ctx_t *ctx)
83479+
{
83480+
if (redo) {
83481+
return false;
83482+
}
83483+
83484+
ecs_table_range_t range = flecs_query_get_range(
83485+
op, &op->src, EcsQuerySrc, ctx);
83486+
if (range.table->flags & EcsTableHasChildOf) {
83487+
return flecs_query_and(op, redo, ctx);
83488+
}
83489+
83490+
if (!(range.table->flags & EcsTableHasParent)) {
83491+
/* If table doesn't have ChildOf or Parent its entities don't have
83492+
* parents. */
83493+
return false;
83494+
}
83495+
83496+
ecs_id_t id = flecs_query_op_get_id(op, ctx);
83497+
ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL);
83498+
83499+
ecs_entity_t tgt = ECS_PAIR_SECOND(id);
83500+
if (tgt == EcsWildcard || tgt == EcsAny) {
83501+
/* Entities in table have parents, so wildcard will always match. */
83502+
return true;
83503+
}
83504+
83505+
/* TODO: if a ChildOf query is constrained to a table with Parent component,
83506+
* only some entities in the table may match the query. TBD on what the
83507+
* behavior should be in this case. For now the query engine only supports
83508+
* constraining the query to a single entity or an entire table. */
83509+
ecs_assert(range.count < 2, ECS_UNSUPPORTED,
83510+
"can only use set_var with single entity for ChildOf($this, parent) terms");
83511+
83512+
if (range.count == 0) {
83513+
/* If matching the entire table, return true. Even though not all
83514+
* entities in the table may match, this lets us add tables with the
83515+
* Parent component to query caches. */
83516+
return true;
83517+
}
83518+
83519+
const EcsParent *parents = flecs_query_tree_get_parents(range);
83520+
if ((uint32_t)parents->value == tgt) {
83521+
return true;
83522+
}
83523+
83524+
return false;
83525+
}
83526+
83527+
bool flecs_query_children(
83528+
const ecs_query_op_t *op,
83529+
bool redo,
83530+
const ecs_query_run_ctx_t *ctx)
83531+
{
83532+
uint64_t written = ctx->written[ctx->op_index];
83533+
if (written & (1ull << op->src.var)) {
83534+
return flecs_query_children_with(op, redo, ctx);
83535+
} else {
83536+
return flecs_query_children_select(op, redo, ctx);
83537+
}
83538+
}
83539+
8345983540
bool flecs_query_tree_and(
8346083541
const ecs_query_op_t *op,
8346183542
bool redo,
@@ -85207,6 +85288,24 @@ ecs_trav_up_t* flecs_trav_table_up(
8520785288
}
8520885289
}
8520985290

85291+
if ((rel == ecs_pair(EcsChildOf, EcsWildcard) &&
85292+
(flags & EcsTableHasParent)))
85293+
{
85294+
const EcsParent *p = ecs_table_get_id(
85295+
world, table, ecs_id(EcsParent),
85296+
ECS_RECORD_TO_ROW(src_record->row));
85297+
ecs_assert(p != NULL, ECS_INTERNAL_ERROR, NULL);
85298+
85299+
ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache,
85300+
world, p->value, with, rel, cr_with, cr_trav);
85301+
if (up_parent->tr) {
85302+
up->src = up_parent->src;
85303+
up->tr = up_parent->tr;
85304+
up->id = up_parent->id;
85305+
goto found;
85306+
}
85307+
}
85308+
8521085309
ecs_trav_up_t up_pair = {0};
8521185310
int32_t r_column = flecs_trav_type_search(
8521285311
&up_pair, table, cr_trav, &type);

src/observable.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1355,7 +1355,7 @@ void flecs_emit(
13551355
}
13561356

13571357
/* Forward events for Parent component as ChildOf pairs. */
1358-
if (id == ecs_id(EcsParent)) {
1358+
if (id == ecs_id(EcsParent) && !table_event) {
13591359
ecs_event_desc_t pdesc = *desc;
13601360

13611361
pdesc.event = event;

src/observer.c

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,11 @@ void flecs_uni_observer_register(
215215
}
216216

217217
if (term && (term->trav == EcsChildOf)) {
218-
flecs_register_observer_for_id(world, observable, o,
219-
offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent));
218+
ecs_assert(o->query != NULL, ECS_INTERNAL_ERROR, NULL);
219+
if (o->query->flags & EcsQueryTableOnly) {
220+
flecs_register_observer_for_id(world, observable, o,
221+
offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent));
222+
}
220223
}
221224
}
222225

@@ -298,8 +301,10 @@ void flecs_unregister_observer(
298301
}
299302

300303
if (term && (term->trav == EcsChildOf)) {
301-
flecs_unregister_observer_for_id(world, observable, o,
302-
offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent));
304+
if (o->query->flags & EcsQueryTableOnly) {
305+
flecs_unregister_observer_for_id(world, observable, o,
306+
offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent));
307+
}
303308
}
304309
}
305310

@@ -1050,7 +1055,7 @@ ecs_observer_t* flecs_observer_init(
10501055
/* Only do optimization when not in sanitized mode. This ensures that the
10511056
* behavior is consistent between observers with and without queries, as
10521057
* both paths will be exercised in unit tests. */
1053-
// #ifndef FLECS_SANITIZE
1058+
#ifndef FLECS_SANITIZE
10541059
/* Temporary arrays for dummy query */
10551060
ecs_term_t terms[FLECS_TERM_COUNT_MAX] = {0};
10561061
ecs_size_t sizes[FLECS_TERM_COUNT_MAX] = {0};
@@ -1072,8 +1077,10 @@ ecs_observer_t* flecs_observer_init(
10721077
(dummy_query.flags & EcsQueryMatchOnlySelf) &&
10731078
!dummy_query.row_fields;
10741079
if (trivial_observer) {
1075-
dummy_query.flags |= desc->query.flags;
1076-
query = &dummy_query;
1080+
if (ECS_PAIR_FIRST(dummy_query.terms[0].id) != EcsChildOf) {
1081+
dummy_query.flags |= desc->query.flags;
1082+
query = &dummy_query;
1083+
}
10771084
} else {
10781085
/* We're going to create an actual query, so undo the keep_alive
10791086
* increment of the dummy_query. */
@@ -1085,7 +1092,7 @@ ecs_observer_t* flecs_observer_init(
10851092
}
10861093
}
10871094
}
1088-
// #endif
1095+
#endif
10891096

10901097
/* Create query */
10911098
if (!query) {

src/query/cache/cache.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,7 @@ ecs_query_cache_t* flecs_query_cache_init(
697697
ecs_os_memcpy_n(observer_desc.query.terms, q->terms,
698698
ecs_term_t, q->term_count);
699699
observer_desc.query.expr = NULL; /* Already parsed */
700+
observer_desc.query.flags |= EcsQueryTableOnly;
700701

701702
result->observer = flecs_observer_init(world, entity, &observer_desc);
702703
if (!result->observer) {

src/query/engine/eval_tree.c

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55

66
#include "../../private_api.h"
77

8+
static
9+
const EcsParent* flecs_query_tree_get_parents(
10+
ecs_table_range_t range)
11+
{
12+
int32_t parent_column = range.table->component_map[ecs_id(EcsParent)];
13+
ecs_assert(parent_column != -1, ECS_INTERNAL_ERROR, NULL);
14+
return ecs_table_get_column(range.table, parent_column - 1, range.offset);
15+
}
16+
817
static
918
bool flecs_query_tree_select_tgt(
1019
const ecs_query_op_t *op,
@@ -325,10 +334,7 @@ bool flecs_query_tree_with(
325334
}
326335

327336
/* Non-fragmenting childof */
328-
int32_t parent_column = range.table->component_map[ecs_id(EcsParent)];
329-
ecs_assert(parent_column != -1, ECS_INTERNAL_ERROR, NULL);
330-
EcsParent *parents = ecs_table_get_column(
331-
range.table, parent_column - 1, range.offset);
337+
const EcsParent *parents = flecs_query_tree_get_parents(range);
332338

333339
/* Evaluating a single entity */
334340
if (range.count == 1) {
@@ -439,7 +445,8 @@ bool flecs_query_tree_with_pre(
439445
return false;
440446
}
441447

442-
bool flecs_query_children(
448+
static
449+
bool flecs_query_children_select(
443450
const ecs_query_op_t *op,
444451
bool redo,
445452
const ecs_query_run_ctx_t *ctx)
@@ -482,6 +489,72 @@ bool flecs_query_children(
482489
return true;
483490
}
484491

492+
static
493+
bool flecs_query_children_with(
494+
const ecs_query_op_t *op,
495+
bool redo,
496+
const ecs_query_run_ctx_t *ctx)
497+
{
498+
if (redo) {
499+
return false;
500+
}
501+
502+
ecs_table_range_t range = flecs_query_get_range(
503+
op, &op->src, EcsQuerySrc, ctx);
504+
if (range.table->flags & EcsTableHasChildOf) {
505+
return flecs_query_and(op, redo, ctx);
506+
}
507+
508+
if (!(range.table->flags & EcsTableHasParent)) {
509+
/* If table doesn't have ChildOf or Parent its entities don't have
510+
* parents. */
511+
return false;
512+
}
513+
514+
ecs_id_t id = flecs_query_op_get_id(op, ctx);
515+
ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL);
516+
517+
ecs_entity_t tgt = ECS_PAIR_SECOND(id);
518+
if (tgt == EcsWildcard || tgt == EcsAny) {
519+
/* Entities in table have parents, so wildcard will always match. */
520+
return true;
521+
}
522+
523+
/* TODO: if a ChildOf query is constrained to a table with Parent component,
524+
* only some entities in the table may match the query. TBD on what the
525+
* behavior should be in this case. For now the query engine only supports
526+
* constraining the query to a single entity or an entire table. */
527+
ecs_assert(range.count < 2, ECS_UNSUPPORTED,
528+
"can only use set_var with single entity for ChildOf($this, parent) terms");
529+
530+
if (range.count == 0) {
531+
/* If matching the entire table, return true. Even though not all
532+
* entities in the table may match, this lets us add tables with the
533+
* Parent component to query caches. */
534+
return true;
535+
}
536+
537+
const EcsParent *parents = flecs_query_tree_get_parents(range);
538+
if ((uint32_t)parents->value == tgt) {
539+
return true;
540+
}
541+
542+
return false;
543+
}
544+
545+
bool flecs_query_children(
546+
const ecs_query_op_t *op,
547+
bool redo,
548+
const ecs_query_run_ctx_t *ctx)
549+
{
550+
uint64_t written = ctx->written[ctx->op_index];
551+
if (written & (1ull << op->src.var)) {
552+
return flecs_query_children_with(op, redo, ctx);
553+
} else {
554+
return flecs_query_children_select(op, redo, ctx);
555+
}
556+
}
557+
485558
bool flecs_query_tree_and(
486559
const ecs_query_op_t *op,
487560
bool redo,

0 commit comments

Comments
 (0)