From e7e3d33d2e59e31fd6a629ed87fcd4724e6d5d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kalp=20=C3=96zcan?= Date: Sun, 1 Mar 2026 20:14:14 +1100 Subject: [PATCH] Fix toggle mask compilation and evaluation with ored terms --- distr/flecs.c | 71 ++++++++++++++++++-- src/query/compiler/compiler.c | 16 +++-- src/query/engine/eval_toggle.c | 55 +++++++++++++++- test/query/project.json | 2 + test/query/src/Toggle.c | 116 +++++++++++++++++++++++++++++++++ test/query/src/main.c | 12 +++- 6 files changed, 261 insertions(+), 11 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index d111cbb5a4..ebc36a897d 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -79960,13 +79960,15 @@ int flecs_query_insert_toggle( continue; } + ecs_flags64_t field_bit = 1llu << term->field_index; + /* Source matches, set flag */ if (term->oper == EcsNot) { - not_toggles |= (1llu << j); + not_toggles |= field_bit; } else if (term->oper == EcsOptional) { - optional_toggles |= (1llu << j); + optional_toggles |= field_bit; } else { - and_toggles |= (1llu << j); + and_toggles |= field_bit; } fields_done |= (1llu << j); @@ -79999,11 +80001,15 @@ int flecs_query_insert_toggle( * set, separate instructions let the query engine backtrack to get * the right results. */ if (optional_toggles) { + ecs_flags64_t optional_done = 0; for (j = i; j < term_count; j ++) { - uint64_t field_bit = 1ull << j; + uint64_t field_bit = 1ull << terms[j].field_index; if (!(optional_toggles & field_bit)) { continue; } + if (optional_done & field_bit) { + continue; + } ecs_query_op_t op = {0}; op.kind = EcsQueryToggleOption; @@ -80011,6 +80017,8 @@ int flecs_query_insert_toggle( op.first.entity = field_bit; op.flags = cur.flags; flecs_query_op_insert(&op, ctx); + + optional_done |= field_bit; } } } @@ -85194,6 +85202,7 @@ static inline int32_t flecs_ctz64(uint64_t v) { static flecs_query_row_mask_t flecs_query_get_row_mask( ecs_iter_t *it, + const ecs_query_t *q, ecs_table_t *table, int32_t block_index, ecs_flags64_t and_fields, @@ -85219,6 +85228,57 @@ flecs_query_row_mask_t flecs_query_get_row_mask( ecs_abort(ECS_INTERNAL_ERROR, NULL); } + ecs_term_t *field_term = NULL; + int32_t ti; + for (ti = 0; ti < q->term_count; ti ++) { + if (q->terms[ti].field_index == i) { + field_term = &q->terms[ti]; + break; + } + } + + bool is_or = false; + if (field_term) { + is_or = field_term->oper == EcsOr || + ((field_term != q->terms) && (field_term[-1].oper == EcsOr)); + } + + if (is_or) { + int32_t start = flecs_itoi32(field_term - q->terms); + int32_t end = start; + + while (start && q->terms[start - 1].oper == EcsOr) { + start --; + } + while (end < (q->term_count - 1) && q->terms[end].oper == EcsOr) { + end ++; + } + + ecs_flags64_t block = 0; + bool chain_has_bitset = false; + + int32_t j; + for (j = start; j <= end; j ++) { + ecs_id_t id = q->terms[j].id; + ecs_bitset_t *bs = flecs_table_get_toggle(table, id); + if (bs) { + ecs_assert((64 * block_index) < bs->size, ECS_INTERNAL_ERROR, NULL); + block |= bs->data[block_index]; + chain_has_bitset = true; + } else if (ecs_table_has_id(it->world, table, id)) { + block = UINT64_MAX; + break; + } + } + + if (chain_has_bitset || block == UINT64_MAX) { + mask &= block; + has_bitset = has_bitset || chain_has_bitset; + } + + continue; + } + ecs_id_t id = it->ids[i]; ecs_bitset_t *bs = flecs_table_get_toggle(table, id); if (!bs) { @@ -85368,7 +85428,8 @@ bool flecs_query_toggle_cmp( block_index = op_ctx->block_index = new_block_index; flecs_query_row_mask_t row_mask = flecs_query_get_row_mask( - it, table, block_index, and_fields, not_fields, op_ctx); + it, &ctx->query->pub, table, block_index, + and_fields, not_fields, op_ctx); /* If table doesn't have bitset columns, all columns match */ if (!(op_ctx->has_bitset = row_mask.has_bitset)) { diff --git a/src/query/compiler/compiler.c b/src/query/compiler/compiler.c index 0a06df19d1..fb9d6ed0ef 100644 --- a/src/query/compiler/compiler.c +++ b/src/query/compiler/compiler.c @@ -770,13 +770,15 @@ int flecs_query_insert_toggle( continue; } + ecs_flags64_t field_bit = 1llu << term->field_index; + /* Source matches, set flag */ if (term->oper == EcsNot) { - not_toggles |= (1llu << j); + not_toggles |= field_bit; } else if (term->oper == EcsOptional) { - optional_toggles |= (1llu << j); + optional_toggles |= field_bit; } else { - and_toggles |= (1llu << j); + and_toggles |= field_bit; } fields_done |= (1llu << j); @@ -809,11 +811,15 @@ int flecs_query_insert_toggle( * set, separate instructions let the query engine backtrack to get * the right results. */ if (optional_toggles) { + ecs_flags64_t optional_done = 0; for (j = i; j < term_count; j ++) { - uint64_t field_bit = 1ull << j; + uint64_t field_bit = 1ull << terms[j].field_index; if (!(optional_toggles & field_bit)) { continue; } + if (optional_done & field_bit) { + continue; + } ecs_query_op_t op = {0}; op.kind = EcsQueryToggleOption; @@ -821,6 +827,8 @@ int flecs_query_insert_toggle( op.first.entity = field_bit; op.flags = cur.flags; flecs_query_op_insert(&op, ctx); + + optional_done |= field_bit; } } } diff --git a/src/query/engine/eval_toggle.c b/src/query/engine/eval_toggle.c index 4b27e3bc53..e44eb5e1ba 100644 --- a/src/query/engine/eval_toggle.c +++ b/src/query/engine/eval_toggle.c @@ -31,6 +31,7 @@ static inline int32_t flecs_ctz64(uint64_t v) { static flecs_query_row_mask_t flecs_query_get_row_mask( ecs_iter_t *it, + const ecs_query_t *q, ecs_table_t *table, int32_t block_index, ecs_flags64_t and_fields, @@ -56,6 +57,57 @@ flecs_query_row_mask_t flecs_query_get_row_mask( ecs_abort(ECS_INTERNAL_ERROR, NULL); } + ecs_term_t *field_term = NULL; + int32_t ti; + for (ti = 0; ti < q->term_count; ti ++) { + if (q->terms[ti].field_index == i) { + field_term = &q->terms[ti]; + break; + } + } + + bool is_or = false; + if (field_term) { + is_or = field_term->oper == EcsOr || + ((field_term != q->terms) && (field_term[-1].oper == EcsOr)); + } + + if (is_or) { + int32_t start = flecs_itoi32(field_term - q->terms); + int32_t end = start; + + while (start && q->terms[start - 1].oper == EcsOr) { + start --; + } + while (end < (q->term_count - 1) && q->terms[end].oper == EcsOr) { + end ++; + } + + ecs_flags64_t block = 0; + bool chain_has_bitset = false; + + int32_t j; + for (j = start; j <= end; j ++) { + ecs_id_t id = q->terms[j].id; + ecs_bitset_t *bs = flecs_table_get_toggle(table, id); + if (bs) { + ecs_assert((64 * block_index) < bs->size, ECS_INTERNAL_ERROR, NULL); + block |= bs->data[block_index]; + chain_has_bitset = true; + } else if (ecs_table_has_id(it->world, table, id)) { + block = UINT64_MAX; + break; + } + } + + if (chain_has_bitset || block == UINT64_MAX) { + mask &= block; + has_bitset = has_bitset || chain_has_bitset; + } + + continue; + } + ecs_id_t id = it->ids[i]; ecs_bitset_t *bs = flecs_table_get_toggle(table, id); if (!bs) { @@ -205,7 +257,8 @@ bool flecs_query_toggle_cmp( block_index = op_ctx->block_index = new_block_index; flecs_query_row_mask_t row_mask = flecs_query_get_row_mask( - it, table, block_index, and_fields, not_fields, op_ctx); + it, &ctx->query->pub, table, block_index, + and_fields, not_fields, op_ctx); /* If table doesn't have bitset columns, all columns match */ if (!(op_ctx->has_bitset = row_mask.has_bitset)) { diff --git a/test/query/project.json b/test/query/project.json index 146b3f66a9..284e948f3c 100644 --- a/test/query/project.json +++ b/test/query/project.json @@ -2082,6 +2082,8 @@ "fixed_2_src_w_toggle", "this_w_fixed_src_w_toggle", "fixed_src_w_this_w_toggle", + "or_chain_3_toggleable_tags", + "or_chain_3_toggleable_components", "this_from_nothing", "this", "this_skip_initial", diff --git a/test/query/src/Toggle.c b/test/query/src/Toggle.c index 2669994d08..3a7fc0d225 100644 --- a/test/query/src/Toggle.c +++ b/test/query/src/Toggle.c @@ -6165,3 +6165,119 @@ void Toggle_toggle_0_src(void) { ecs_fini(world); } + +static +int32_t toggle_or_match_count( + ecs_world_t *world, + ecs_query_t *q) +{ + int32_t count = 0; + ecs_iter_t it = ecs_query_iter(world, q); + while (ecs_query_next(&it)) { + count += it.count; + } + return count; +} + +void Toggle_or_chain_3_toggleable_tags(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + ECS_TAG(world, TagC); + + ecs_add_id(world, TagA, EcsCanToggle); + ecs_add_id(world, TagB, EcsCanToggle); + ecs_add_id(world, TagC, EcsCanToggle); + + ecs_entity_t e = ecs_new_w_id(world, TagA); + ecs_add(world, e, TagB); + ecs_add(world, e, TagC); + + ecs_enable_id(world, e, TagA, false); + ecs_enable_id(world, e, TagB, false); + ecs_enable_id(world, e, TagC, false); + + ecs_query_t *q = ecs_query(world, { + .terms = { + { .id = TagA, .oper = EcsOr }, + { .id = TagB, .oper = EcsOr }, + { .id = TagC } + }, + .cache_kind = cache_kind + }); + test_assert(q != NULL); + + test_int(toggle_or_match_count(world, q), 0); + + ecs_enable_id(world, e, TagA, true); + test_int(toggle_or_match_count(world, q), 1); + + ecs_enable_id(world, e, TagA, false); + ecs_enable_id(world, e, TagB, true); + test_int(toggle_or_match_count(world, q), 1); + + ecs_enable_id(world, e, TagB, false); + ecs_enable_id(world, e, TagC, true); + test_int(toggle_or_match_count(world, q), 1); + + ecs_enable_id(world, e, TagA, true); + ecs_enable_id(world, e, TagB, true); + test_int(toggle_or_match_count(world, q), 1); + + ecs_query_fini(q); + + ecs_fini(world); +} + +void Toggle_or_chain_3_toggleable_components(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + ECS_COMPONENT(world, Velocity); + ECS_COMPONENT(world, Mass); + + ecs_add_id(world, ecs_id(Position), EcsCanToggle); + ecs_add_id(world, ecs_id(Velocity), EcsCanToggle); + ecs_add_id(world, ecs_id(Mass), EcsCanToggle); + + ecs_entity_t e = ecs_new_w(world, Position); + ecs_set(world, e, Position, {10, 20}); + ecs_set(world, e, Velocity, {1, 2}); + ecs_set(world, e, Mass, {100}); + + ecs_enable_component(world, e, Position, false); + ecs_enable_component(world, e, Velocity, false); + ecs_enable_component(world, e, Mass, false); + + ecs_query_t *q = ecs_query(world, { + .terms = { + { .id = ecs_id(Position), .oper = EcsOr }, + { .id = ecs_id(Velocity), .oper = EcsOr }, + { .id = ecs_id(Mass) } + }, + .cache_kind = cache_kind + }); + test_assert(q != NULL); + + test_int(toggle_or_match_count(world, q), 0); + + ecs_enable_component(world, e, Position, true); + test_int(toggle_or_match_count(world, q), 1); + + ecs_enable_component(world, e, Position, false); + ecs_enable_component(world, e, Velocity, true); + test_int(toggle_or_match_count(world, q), 1); + + ecs_enable_component(world, e, Velocity, false); + ecs_enable_component(world, e, Mass, true); + test_int(toggle_or_match_count(world, q), 1); + + ecs_enable_component(world, e, Position, true); + ecs_enable_component(world, e, Velocity, true); + test_int(toggle_or_match_count(world, q), 1); + + ecs_query_fini(q); + + ecs_fini(world); +} diff --git a/test/query/src/main.c b/test/query/src/main.c index 1638c5e227..88d1ad9d1f 100644 --- a/test/query/src/main.c +++ b/test/query/src/main.c @@ -2004,6 +2004,8 @@ void Toggle_fixed_src_2_component_toggle(void); void Toggle_fixed_2_src_w_toggle(void); void Toggle_this_w_fixed_src_w_toggle(void); void Toggle_fixed_src_w_this_w_toggle(void); +void Toggle_or_chain_3_toggleable_tags(void); +void Toggle_or_chain_3_toggleable_components(void); void Toggle_this_from_nothing(void); void Toggle_this(void); void Toggle_this_skip_initial(void); @@ -10629,6 +10631,14 @@ bake_test_case Toggle_testcases[] = { "fixed_src_w_this_w_toggle", Toggle_fixed_src_w_this_w_toggle }, + { + "or_chain_3_toggleable_tags", + Toggle_or_chain_3_toggleable_tags + }, + { + "or_chain_3_toggleable_components", + Toggle_or_chain_3_toggleable_components + }, { "this_from_nothing", Toggle_this_from_nothing @@ -13931,7 +13941,7 @@ static bake_test_suite suites[] = { "Toggle", Toggle_setup, NULL, - 163, + 165, Toggle_testcases, 1, Toggle_params