From fb75dd15f2bbc21f99bd610fc2c300e6c0d41db9 Mon Sep 17 00:00:00 2001 From: Alp Date: Wed, 25 Mar 2026 16:03:30 +1100 Subject: [PATCH 1/4] query: use actual table id for IsA matches in fixed traversal Updates `flecs_query_trav_fixed_src_up_fixed_second` to use the actual component ID stored in the table's type array when a match is found for an `EcsIsA` relationship, rather than reconstructing the pair from arguments. Changes: - Capture the column index returned by `ecs_search_relation`. - Add a check for `column != -1` to ensure the relationship was found in the table (Self) before accessing the type array. - When `trav == EcsIsA`, set the matched ID to `table->type.array[column]`. - Manually populate the iterator fields and variables with the resolved `matched` ID. This ensures that the query iterator reports the exact component ID present in the archetype when matching `IsA` relationships. --- distr/flecs.c | 18 ++++++++++++++---- src/query/engine/eval_trav.c | 18 ++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index a277b96272..5f9f19bfc3 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -85722,10 +85722,10 @@ bool flecs_query_trav_fixed_src_up_fixed_second( /* Check if table has transitive relationship by traversing upwards */ ecs_table_record_t *tr = NULL; - ecs_search_relation(ctx->world, table, 0, - ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, &tr); + const int32_t column = ecs_search_relation(ctx->world, table, 0, + ecs_pair(trav, second), trav, EcsSelf| EcsUp, NULL, NULL, &tr); - if (!tr) { + if (column == -1 || !tr) { if (op->match_flags & EcsTermReflexive) { return flecs_query_trav_fixed_src_reflexive(op, ctx, &range, trav, second); @@ -85734,7 +85734,17 @@ bool flecs_query_trav_fixed_src_up_fixed_second( } } - flecs_query_set_trav_match(op, tr, trav, second, ctx); + ecs_id_t matched = ecs_pair(trav, second); + if (trav == EcsIsA) { + matched = table->type.array[column]; + } + + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + it->ids [op->field_index] = matched; + flecs_query_it_set_tr(it, op->field_index, tr); + } + flecs_query_set_vars(op, matched, ctx); return true; } diff --git a/src/query/engine/eval_trav.c b/src/query/engine/eval_trav.c index 00177c982f..f5166d305e 100644 --- a/src/query/engine/eval_trav.c +++ b/src/query/engine/eval_trav.c @@ -96,10 +96,10 @@ bool flecs_query_trav_fixed_src_up_fixed_second( /* Check if table has transitive relationship by traversing upwards */ ecs_table_record_t *tr = NULL; - ecs_search_relation(ctx->world, table, 0, - ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, &tr); + const int32_t column = ecs_search_relation(ctx->world, table, 0, + ecs_pair(trav, second), trav, EcsSelf| EcsUp, NULL, NULL, &tr); - if (!tr) { + if (column == -1 || !tr) { if (op->match_flags & EcsTermReflexive) { return flecs_query_trav_fixed_src_reflexive(op, ctx, &range, trav, second); @@ -108,7 +108,17 @@ bool flecs_query_trav_fixed_src_up_fixed_second( } } - flecs_query_set_trav_match(op, tr, trav, second, ctx); + ecs_id_t matched = ecs_pair(trav, second); + if (trav == EcsIsA) { + matched = table->type.array[column]; + } + + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + it->ids [op->field_index] = matched; + flecs_query_it_set_tr(it, op->field_index, tr); + } + flecs_query_set_vars(op, matched, ctx); return true; } From 8b06eb5521818277a3582ec197d50bf76289afa1 Mon Sep 17 00:00:00 2001 From: Alp Date: Wed, 25 Mar 2026 16:08:19 +1100 Subject: [PATCH 2/4] [docs] Update the link to Stagehand repo --- docs/Docs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Docs.md b/docs/Docs.md index deb2472b77..b175554105 100644 --- a/docs/Docs.md +++ b/docs/Docs.md @@ -7,7 +7,7 @@ - [Getting Started with Unreal Engine](https://github.com/PreyK/Unreal-Minimum-Viable-Flecs) - [Unreal-Flecs (Unreal Engine Flecs plugin)](https://github.com/Reddy-dev/Unreal-Flecs) - [Godot-flecs-sample (example)](https://github.com/paulfigiel/godot-flecs-sample) -- [Flecs-Godot (GDExtension)](https://github.com/VoxInteractive/Godot-Flecs) +- [Stagehand (GDExtension)](https://github.com/VerdantInteractive/Stagehand) - [Godot-turbo (engine module)](https://github.com/callmefloof/godot-turbo/) ([demo project](https://github.com/callmefloof/godot_turbo_demo_projects)) - [Getting Started with Raylib](https://github.com/kranzky/raylib-flecs-starter-kit) - [Getting Started with SDL3, ImGui, Sqlite](https://github.com/ilyas-taouaou/CodotakuCMakeRepo) ([video](https://www.youtube.com/watch?v=T32B7nf6B-8)) From c518759e3c0755fb32195a372380a23a649f38e1 Mon Sep 17 00:00:00 2001 From: Alp Date: Wed, 25 Mar 2026 22:43:49 +1100 Subject: [PATCH 3/4] Rename column -> index --- distr/flecs.c | 6 +++--- src/query/engine/eval_trav.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/distr/flecs.c b/distr/flecs.c index 5f9f19bfc3..334fac4345 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -85722,10 +85722,10 @@ bool flecs_query_trav_fixed_src_up_fixed_second( /* Check if table has transitive relationship by traversing upwards */ ecs_table_record_t *tr = NULL; - const int32_t column = ecs_search_relation(ctx->world, table, 0, + const int32_t index = ecs_search_relation(ctx->world, table, 0, ecs_pair(trav, second), trav, EcsSelf| EcsUp, NULL, NULL, &tr); - if (column == -1 || !tr) { + if (index == -1 || !tr) { if (op->match_flags & EcsTermReflexive) { return flecs_query_trav_fixed_src_reflexive(op, ctx, &range, trav, second); @@ -85736,7 +85736,7 @@ bool flecs_query_trav_fixed_src_up_fixed_second( ecs_id_t matched = ecs_pair(trav, second); if (trav == EcsIsA) { - matched = table->type.array[column]; + matched = table->type.array[index]; } if (op->field_index != -1) { diff --git a/src/query/engine/eval_trav.c b/src/query/engine/eval_trav.c index f5166d305e..460080362e 100644 --- a/src/query/engine/eval_trav.c +++ b/src/query/engine/eval_trav.c @@ -96,10 +96,10 @@ bool flecs_query_trav_fixed_src_up_fixed_second( /* Check if table has transitive relationship by traversing upwards */ ecs_table_record_t *tr = NULL; - const int32_t column = ecs_search_relation(ctx->world, table, 0, + const int32_t index = ecs_search_relation(ctx->world, table, 0, ecs_pair(trav, second), trav, EcsSelf| EcsUp, NULL, NULL, &tr); - if (column == -1 || !tr) { + if (index == -1 || !tr) { if (op->match_flags & EcsTermReflexive) { return flecs_query_trav_fixed_src_reflexive(op, ctx, &range, trav, second); @@ -110,7 +110,7 @@ bool flecs_query_trav_fixed_src_up_fixed_second( ecs_id_t matched = ecs_pair(trav, second); if (trav == EcsIsA) { - matched = table->type.array[column]; + matched = table->type.array[index]; } if (op->field_index != -1) { From 398c67f4a768a74ca06af92cd47964d0963ae2d2 Mon Sep 17 00:00:00 2001 From: Alp Date: Tue, 31 Mar 2026 11:15:46 +1100 Subject: [PATCH 4/4] Add test Traversal_this_written_up_isa_trav_id_n_lvl --- test/query/project.json | 3 ++- test/query/src/Traversal.c | 46 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/test/query/project.json b/test/query/project.json index 2e814e63ad..3f3387558d 100644 --- a/test/query/project.json +++ b/test/query/project.json @@ -1690,7 +1690,8 @@ "this_or_w_self_up_childof", "this_or_w_self_up_childof_2", "this_or_w_self_up_childof_w_tag", - "this_written_or_w_self_up_childof" + "this_written_or_w_self_up_childof", + "this_written_up_isa_trav_id_n_lvl" ] }, { "id": "Cascade", diff --git a/test/query/src/Traversal.c b/test/query/src/Traversal.c index 90c4a987e0..64648a0edf 100644 --- a/test/query/src/Traversal.c +++ b/test/query/src/Traversal.c @@ -13160,3 +13160,49 @@ void Traversal_this_written_or_w_self_up_childof(void) { ecs_fini(world); } + +void Traversal_this_written_up_isa_trav_id_n_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + ecs_add_pair(world, ecs_id(Position), EcsOnInstantiate, EcsInherit); + + /* Setup: instance --(IsA)--> troll --(IsA)--> enemy + * Position is on troll and inherited by instance. + * Querying (IsA, enemy) on this should match instance and report + * the field ID as (IsA, troll) — the actual pair in the archetype — + * not (IsA, enemy) which was incorrectly reconstructed from arguments + * before the fix in flecs_query_trav_fixed_src_up_fixed_second. */ + ecs_entity_t enemy = ecs_new_w_id(world, EcsPrefab); + ecs_entity_t troll = ecs_new_w_id(world, EcsPrefab); + ecs_add_pair(world, troll, EcsIsA, enemy); + ecs_set(world, troll, Position, {10, 20}); + + ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, troll); + + ecs_query_t *q = ecs_query(world, { + .terms = { + { ecs_id(Position) }, /* constrains 'this' to tables with Position */ + { .id = ecs_pair(EcsIsA, enemy) } /* transitive IsA check */ + }, + .cache_kind = cache_kind + }); + + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + test_uint(instance, it.entities[0]); + test_uint(ecs_id(Position), ecs_field_id(&it, 0)); + test_uint(troll, ecs_field_src(&it, 0)); + /* The field ID for the (IsA, enemy) term must be the actual pair stored + * in the instance's archetype: (IsA, troll), not the query target. */ + test_uint(ecs_pair(EcsIsA, troll), ecs_field_id(&it, 1)); + + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(q); + + ecs_fini(world); +}