diff --git a/docs/Queries.md b/docs/Queries.md index 89b2337da..ab20f5e63 100644 --- a/docs/Queries.md +++ b/docs/Queries.md @@ -1803,6 +1803,7 @@ Traversal behavior can be customized with the following bitflags, in addition to | Down | `down` | `EcsDown` | `flecs::Down` | Match by traversing downwards (derived, cannot be set) | | Parent | `parent` | `EcsParent` | `flecs::Parent` | Short for up(ChildOf) | | Cascade | `cascade` | `EcsCascade` | `flecs::Cascade` | Same as Up, but iterate in breadth-first order | +| Desc | `desc` | `EcsDesc` | `flecs::Desc` | Combine with Cascade to iterate hierarchy bottom to top | If just `Self` is set a query will only match components on the matched entity (no traversal). If just `Up` is set, a query will only match components that can be reached by following the relationship and ignore components from the matched entity. If both `Self` and `Up` are set, the query will first look on the matched entity, and if it does not have the component the query will continue searching by traverse the relationship. diff --git a/flecs.c b/flecs.c index 3a989b045..e7b9d0324 100644 --- a/flecs.c +++ b/flecs.c @@ -1086,7 +1086,7 @@ struct ecs_query_t { /* Monitor generation */ int32_t monitor_generation; - int32_t cascade_by; /* Identify cascade column */ + int32_t cascade_by; /* Identify cascade term */ int32_t match_count; /* How often have tables been (un)matched */ int32_t prev_match_count; /* Track if sorting is needed */ int32_t rematch_count; /* Track which tables were added during rematch */ @@ -17273,12 +17273,26 @@ ecs_query_table_match_t* flecs_query_find_group_insertion_node( ecs_map_iter_t it = ecs_map_iter(&query->groups); ecs_query_table_list_t *list, *closest_list = NULL; uint64_t id, closest_id = 0; + + bool desc = false; + + if (query->cascade_by) { + desc = (query->filter.terms[ + query->cascade_by - 1].src.flags & EcsDesc) != 0; + } /* Find closest smaller group id */ while (ecs_map_next(&it)) { id = ecs_map_key(&it); - if (id >= group_id) { - continue; + + if (!desc) { + if (id >= group_id) { + continue; + } + } else { + if (id <= group_id) { + continue; + } } list = ecs_map_ptr(&it); @@ -17287,7 +17301,14 @@ ecs_query_table_match_t* flecs_query_find_group_insertion_node( continue; } - if (!closest_list || ((group_id - id) < (group_id - closest_id))) { + bool comp; + if (!desc) { + comp = ((group_id - id) < (group_id - closest_id)); + } else { + comp = ((group_id - id) > (group_id - closest_id)); + } + + if (!closest_list || comp) { closest_id = id; closest_list = list; } @@ -30153,6 +30174,7 @@ void FlecsMonitorImport( #define TOK_DOWN "down" #define TOK_CASCADE "cascade" #define TOK_PARENT "parent" +#define TOK_DESC "desc" #define TOK_OVERRIDE "OVERRIDE" #define TOK_ROLE_AND "AND" @@ -30497,6 +30519,8 @@ uint8_t flecs_parse_set_token( return EcsDown; } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { return EcsCascade; + } else if (!ecs_os_strcmp(token, TOK_DESC)) { + return EcsDesc; } else if (!ecs_os_strcmp(token, TOK_PARENT)) { return EcsParent; } else { @@ -30663,6 +30687,7 @@ const char* flecs_parse_arguments( /* Check for term flags */ } else if (!ecs_os_strcmp(token, TOK_CASCADE) || + !ecs_os_strcmp(token, TOK_DESC) || !ecs_os_strcmp(token, TOK_SELF) || !ecs_os_strcmp(token, TOK_UP) || !ecs_os_strcmp(token, TOK_DOWN) || diff --git a/flecs.h b/flecs.h index 5ccc97994..45ded224f 100644 --- a/flecs.h +++ b/flecs.h @@ -2905,12 +2905,13 @@ typedef enum ecs_oper_kind_t { #define EcsDown (1u << 3) /**< Match by traversing downwards (derived, cannot be set) */ #define EcsTraverseAll (1u << 4) /**< Match all entities encountered through traversal */ #define EcsCascade (1u << 5) /**< Sort results breadth first */ -#define EcsParent (1u << 6) /**< Short for up(ChildOf) */ -#define EcsIsVariable (1u << 7) /**< Term id is a variable */ -#define EcsIsEntity (1u << 8) /**< Term id is an entity */ -#define EcsIsName (1u << 9) /**< Term id is a name (don't attempt to lookup as entity) */ -#define EcsFilter (1u << 10) /**< Prevent observer from triggering on term */ -#define EcsTraverseFlags (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsParent) +#define EcsDesc (1u << 6) /**< Iterate groups in descending order */ +#define EcsParent (1u << 7) /**< Short for up(ChildOf) */ +#define EcsIsVariable (1u << 8) /**< Term id is a variable */ +#define EcsIsEntity (1u << 9) /**< Term id is an entity */ +#define EcsIsName (1u << 10) /**< Term id is a name (don't attempt to lookup as entity) */ +#define EcsFilter (1u << 11) /**< Prevent observer from triggering on term */ +#define EcsTraverseFlags (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsDesc|EcsParent) /* Term flags discovered & set during filter creation. Mostly used internally to * store information relevant to queries. */ @@ -15842,6 +15843,7 @@ static const uint32_t Self = EcsSelf; static const uint32_t Up = EcsUp; static const uint32_t Down = EcsDown; static const uint32_t Cascade = EcsCascade; +static const uint32_t Desc = EcsDesc; static const uint32_t Parent = EcsParent; static const uint32_t IsVariable = EcsIsVariable; static const uint32_t IsEntity = EcsIsEntity; @@ -26862,6 +26864,13 @@ struct term_id_builder_i { return this->cascade(_::cpp_type::id(this->world_v())); } + /* Use with cascade to iterate results in descending (bottom -> top) order */ + Base& desc() { + this->assert_term_id(); + m_term_id->flags |= flecs::Desc; + return *this; + } + /* The parent flag is short for up(flecs::ChildOf) */ Base& parent() { this->assert_term_id(); diff --git a/include/flecs.h b/include/flecs.h index 482161594..cf4c346b1 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -673,12 +673,13 @@ typedef enum ecs_oper_kind_t { #define EcsDown (1u << 3) /**< Match by traversing downwards (derived, cannot be set) */ #define EcsTraverseAll (1u << 4) /**< Match all entities encountered through traversal */ #define EcsCascade (1u << 5) /**< Sort results breadth first */ -#define EcsParent (1u << 6) /**< Short for up(ChildOf) */ -#define EcsIsVariable (1u << 7) /**< Term id is a variable */ -#define EcsIsEntity (1u << 8) /**< Term id is an entity */ -#define EcsIsName (1u << 9) /**< Term id is a name (don't attempt to lookup as entity) */ -#define EcsFilter (1u << 10) /**< Prevent observer from triggering on term */ -#define EcsTraverseFlags (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsParent) +#define EcsDesc (1u << 6) /**< Iterate groups in descending order */ +#define EcsParent (1u << 7) /**< Short for up(ChildOf) */ +#define EcsIsVariable (1u << 8) /**< Term id is a variable */ +#define EcsIsEntity (1u << 9) /**< Term id is an entity */ +#define EcsIsName (1u << 10) /**< Term id is a name (don't attempt to lookup as entity) */ +#define EcsFilter (1u << 11) /**< Prevent observer from triggering on term */ +#define EcsTraverseFlags (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsDesc|EcsParent) /* Term flags discovered & set during filter creation. Mostly used internally to * store information relevant to queries. */ diff --git a/include/flecs/addons/cpp/c_types.hpp b/include/flecs/addons/cpp/c_types.hpp index 68c6eac4a..477cb4c06 100644 --- a/include/flecs/addons/cpp/c_types.hpp +++ b/include/flecs/addons/cpp/c_types.hpp @@ -91,6 +91,7 @@ static const uint32_t Self = EcsSelf; static const uint32_t Up = EcsUp; static const uint32_t Down = EcsDown; static const uint32_t Cascade = EcsCascade; +static const uint32_t Desc = EcsDesc; static const uint32_t Parent = EcsParent; static const uint32_t IsVariable = EcsIsVariable; static const uint32_t IsEntity = EcsIsEntity; diff --git a/include/flecs/addons/cpp/mixins/term/builder_i.hpp b/include/flecs/addons/cpp/mixins/term/builder_i.hpp index 18faba945..6e1cef3cb 100644 --- a/include/flecs/addons/cpp/mixins/term/builder_i.hpp +++ b/include/flecs/addons/cpp/mixins/term/builder_i.hpp @@ -63,6 +63,13 @@ struct term_id_builder_i { return this->cascade(_::cpp_type::id(this->world_v())); } + /* Use with cascade to iterate results in descending (bottom -> top) order */ + Base& desc() { + this->assert_term_id(); + m_term_id->flags |= flecs::Desc; + return *this; + } + /* The parent flag is short for up(flecs::ChildOf) */ Base& parent() { this->assert_term_id(); diff --git a/src/addons/parser.c b/src/addons/parser.c index a967daa76..ebaabd393 100644 --- a/src/addons/parser.c +++ b/src/addons/parser.c @@ -33,6 +33,7 @@ #define TOK_DOWN "down" #define TOK_CASCADE "cascade" #define TOK_PARENT "parent" +#define TOK_DESC "desc" #define TOK_OVERRIDE "OVERRIDE" #define TOK_ROLE_AND "AND" @@ -377,6 +378,8 @@ uint8_t flecs_parse_set_token( return EcsDown; } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { return EcsCascade; + } else if (!ecs_os_strcmp(token, TOK_DESC)) { + return EcsDesc; } else if (!ecs_os_strcmp(token, TOK_PARENT)) { return EcsParent; } else { @@ -543,6 +546,7 @@ const char* flecs_parse_arguments( /* Check for term flags */ } else if (!ecs_os_strcmp(token, TOK_CASCADE) || + !ecs_os_strcmp(token, TOK_DESC) || !ecs_os_strcmp(token, TOK_SELF) || !ecs_os_strcmp(token, TOK_UP) || !ecs_os_strcmp(token, TOK_DOWN) || diff --git a/src/private_types.h b/src/private_types.h index cd55322db..566855078 100644 --- a/src/private_types.h +++ b/src/private_types.h @@ -251,7 +251,7 @@ struct ecs_query_t { /* Monitor generation */ int32_t monitor_generation; - int32_t cascade_by; /* Identify cascade column */ + int32_t cascade_by; /* Identify cascade term */ int32_t match_count; /* How often have tables been (un)matched */ int32_t prev_match_count; /* Track if sorting is needed */ int32_t rematch_count; /* Track which tables were added during rematch */ diff --git a/src/query.c b/src/query.c index 472f3970b..0a7b64f1e 100644 --- a/src/query.c +++ b/src/query.c @@ -119,12 +119,26 @@ ecs_query_table_match_t* flecs_query_find_group_insertion_node( ecs_map_iter_t it = ecs_map_iter(&query->groups); ecs_query_table_list_t *list, *closest_list = NULL; uint64_t id, closest_id = 0; + + bool desc = false; + + if (query->cascade_by) { + desc = (query->filter.terms[ + query->cascade_by - 1].src.flags & EcsDesc) != 0; + } /* Find closest smaller group id */ while (ecs_map_next(&it)) { id = ecs_map_key(&it); - if (id >= group_id) { - continue; + + if (!desc) { + if (id >= group_id) { + continue; + } + } else { + if (id <= group_id) { + continue; + } } list = ecs_map_ptr(&it); @@ -133,7 +147,14 @@ ecs_query_table_match_t* flecs_query_find_group_insertion_node( continue; } - if (!closest_list || ((group_id - id) < (group_id - closest_id))) { + bool comp; + if (!desc) { + comp = ((group_id - id) < (group_id - closest_id)); + } else { + comp = ((group_id - id) > (group_id - closest_id)); + } + + if (!closest_list || comp) { closest_id = id; closest_list = list; } diff --git a/test/addons/project.json b/test/addons/project.json index b6f0d8b16..4b29c886b 100644 --- a/test/addons/project.json +++ b/test/addons/project.json @@ -244,7 +244,8 @@ "pair_3_args_implicit_this", "pair_4_args", "pair_4_args_implicit_this", - "pair_3_args_2_terms" + "pair_3_args_2_terms", + "cascade_desc" ] }, { "id": "Plecs", diff --git a/test/addons/src/Parser.c b/test/addons/src/Parser.c index 2ecedb13c..925434527 100644 --- a/test/addons/src/Parser.c +++ b/test/addons/src/Parser.c @@ -5532,3 +5532,26 @@ void Parser_pair_3_args_2_terms(void) { ecs_fini(world); } +void Parser_cascade_desc(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Pred); + + ecs_filter_t f = ECS_FILTER_INIT; + test_assert(NULL != ecs_filter_init(world, &(ecs_filter_desc_t){ + .storage = &f, + .expr = "Pred(cascade|desc)" + })); + test_int(filter_count(&f), 1); + + ecs_term_t *terms = filter_terms(&f); + test_first(terms[0], Pred, EcsSelf|EcsIsEntity); + test_src(terms[0], EcsThis, EcsUp|EcsCascade|EcsDesc|EcsIsVariable); + test_int(terms[0].oper, EcsAnd); + test_int(terms[0].inout, EcsInOutDefault); + test_int(terms[0].src.trav, EcsIsA); + + ecs_filter_fini(&f); + + ecs_fini(world); +} diff --git a/test/addons/src/main.c b/test/addons/src/main.c index c3c4af393..5952ae797 100644 --- a/test/addons/src/main.c +++ b/test/addons/src/main.c @@ -240,6 +240,7 @@ void Parser_pair_3_args_implicit_this(void); void Parser_pair_4_args(void); void Parser_pair_4_args_implicit_this(void); void Parser_pair_3_args_2_terms(void); +void Parser_cascade_desc(void); // Testsuite 'Plecs' void Plecs_null(void); @@ -2519,6 +2520,10 @@ bake_test_case Parser_testcases[] = { { "pair_3_args_2_terms", Parser_pair_3_args_2_terms + }, + { + "cascade_desc", + Parser_cascade_desc } }; @@ -7741,7 +7746,7 @@ static bake_test_suite suites[] = { "Parser", NULL, NULL, - 231, + 232, Parser_testcases }, { diff --git a/test/api/project.json b/test/api/project.json index 097132e73..4d87fe3fa 100644 --- a/test/api/project.json +++ b/test/api/project.json @@ -1598,6 +1598,8 @@ "new_custom_rel_cascade", "cascade_w_2_depths", "cascade_w_3_depths", + "cascade_w_2_depths_desc", + "cascade_w_3_depths_desc", "not_pair_relation_wildcard", "not_pair_object_wildcard", "two_pair_wildcards_one_not", @@ -1630,6 +1632,9 @@ "cascade_rematch_2_lvls", "cascade_rematch_2_lvls_2_relations", "cascade_topological", + "cascade_desc_rematch_2_lvls", + "cascade_desc_rematch_2_lvls_2_relations", + "cascade_desc_topological", "childof_rematch_from_isa", "rematch_optional_ref", "rematch_optional_ref_w_2_refs", diff --git a/test/api/src/Query.c b/test/api/src/Query.c index 615db2f39..292a7f791 100644 --- a/test/api/src/Query.c +++ b/test/api/src/Query.c @@ -7450,6 +7450,92 @@ void Query_cascade_w_3_depths(void) { ecs_fini(world); } +void Query_cascade_w_2_depths_desc(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_ENTITY(world, Rel, EcsTraversable); + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + ecs_query_t *q = ecs_query_new(world, "Tag(cascade|desc(Rel))"); + test_assert(q != NULL); + + ecs_entity_t e0 = ecs_new(world, Tag); + ecs_entity_t e1 = ecs_new(world, Tag); + ecs_entity_t e2 = ecs_new_id(world); + ecs_entity_t e3 = ecs_new_id(world); + + ecs_add_pair(world, e1, Rel, e0); + ecs_add_pair(world, e2, Rel, e1); + ecs_add_pair(world, e3, Rel, e2); + + ecs_add_id(world, e2, Foo); /* mix up order */ + ecs_add_id(world, e1, Bar); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e3); + test_uint(it.sources[0], e1); + + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e2); + test_uint(it.sources[0], e1); + + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e1); + test_uint(it.sources[0], e0); + test_bool(false, ecs_query_next(&it)); + + ecs_fini(world); +} + +void Query_cascade_w_3_depths_desc(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_ENTITY(world, Rel, EcsTraversable); + ECS_TAG(world, Foo); + ECS_TAG(world, Bar); + + ecs_query_t *q = ecs_query_new(world, "Tag(cascade|desc(Rel))"); + test_assert(q != NULL); + + ecs_entity_t e0 = ecs_new(world, Tag); + ecs_entity_t e1 = ecs_new(world, Tag); + ecs_entity_t e2 = ecs_new(world, Tag); + ecs_entity_t e3 = ecs_new_id(world); + + ecs_add_pair(world, e1, Rel, e0); + ecs_add_pair(world, e2, Rel, e1); + ecs_add_pair(world, e3, Rel, e2); + + ecs_add_id(world, e2, Foo); /* mix up order */ + ecs_add_id(world, e1, Bar); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e3); + test_uint(it.sources[0], e2); + + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e2); + test_uint(it.sources[0], e1); + + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e1); + test_uint(it.sources[0], e0); + test_bool(false, ecs_query_next(&it)); + + ecs_fini(world); +} + void Query_not_pair_relation_wildcard(void) { ecs_world_t *world = ecs_mini(); @@ -8108,6 +8194,189 @@ void Query_cascade_topological(void) { ecs_fini(world); } +void Query_cascade_desc_rematch_2_lvls(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Position); + + ecs_entity_t e_0 = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e_1 = ecs_set(world, 0, Position, {30, 40}); + ecs_entity_t e_2 = ecs_set(world, 0, Position, {50, 60}); + ecs_entity_t e_3 = ecs_set(world, 0, Position, {70, 80}); + + ecs_add_pair(world, e_3, EcsChildOf, e_2); + ecs_add_pair(world, e_2, EcsChildOf, e_1); + ecs_add_pair(world, e_1, EcsChildOf, e_0); + + ecs_query_t *q = ecs_query_new(world, "Position(cascade|desc(ChildOf))"); + test_assert(q != NULL); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e_3); + test_uint(it.sources[0], e_2); + Position *p = ecs_field(&it, Position, 1); + test_int(p[0].x, 50); + test_int(p[0].y, 60); + + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e_2); + test_uint(it.sources[0], e_1); + p = ecs_field(&it, Position, 1); + test_int(p[0].x, 30); + test_int(p[0].y, 40); + + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e_1); + test_uint(it.sources[0], e_0); + p = ecs_field(&it, Position, 1); + test_int(p[0].x, 10); + test_int(p[0].y, 20); + + test_bool(false, ecs_query_next(&it)); + + ecs_remove_pair(world, e_1, EcsChildOf, EcsWildcard); + ecs_remove_pair(world, e_2, EcsChildOf, EcsWildcard); + ecs_remove_pair(world, e_3, EcsChildOf, EcsWildcard); + + ecs_add_pair(world, e_0, EcsChildOf, e_1); + ecs_add_pair(world, e_1, EcsChildOf, e_2); + ecs_add_pair(world, e_2, EcsChildOf, e_3); + + it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e_0); + test_uint(it.sources[0], e_1); + p = ecs_field(&it, Position, 1); + test_int(p[0].x, 30); + test_int(p[0].y, 40); + + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e_1); + test_uint(it.sources[0], e_2); + p = ecs_field(&it, Position, 1); + test_int(p[0].x, 50); + test_int(p[0].y, 60); + + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e_2); + test_uint(it.sources[0], e_3); + p = ecs_field(&it, Position, 1); + test_int(p[0].x, 70); + test_int(p[0].y, 80); + test_bool(false, ecs_query_next(&it)); + + ecs_fini(world); +} + +void Query_cascade_desc_rematch_2_lvls_2_relations(void) { + ecs_world_t *world = ecs_mini(); + + ECS_ENTITY(world, R, EcsTraversable); + ECS_COMPONENT(world, Position); + + ecs_entity_t e_0 = ecs_set(world, 0, Position, {10, 20}); + ecs_entity_t e_1 = ecs_set(world, 0, Position, {30, 40}); + ecs_entity_t e_2 = ecs_set(world, 0, Position, {50, 60}); + ecs_entity_t e_3 = ecs_set(world, 0, Position, {70, 80}); + + ecs_add_pair(world, e_3, R, e_2); + ecs_add_pair(world, e_2, R, e_1); + ecs_add_pair(world, e_1, R, e_0); + + ecs_entity_t t = ecs_new_id(world); + ecs_add(world, t, Position); + ecs_add_pair(world, t, R, e_2); + ecs_add_pair(world, t, R, e_1); + ecs_delete(world, t); + + ecs_query_t *q = ecs_query_new(world, "Position(cascade|desc(R))"); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e_3); + test_uint(it.sources[0], e_2); + Position *p = ecs_field(&it, Position, 1); + test_int(p[0].x, 50); + test_int(p[0].y, 60); + + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e_2); + test_uint(it.sources[0], e_1); + p = ecs_field(&it, Position, 1); + test_int(p[0].x, 30); + test_int(p[0].y, 40); + + test_bool(true, ecs_query_next(&it)); + test_int(it.count, 1); + test_uint(it.entities[0], e_1); + test_uint(it.sources[0], e_0); + p = ecs_field(&it, Position, 1); + test_int(p[0].x, 10); + test_int(p[0].y, 20); + test_bool(false, ecs_query_next(&it)); + + ecs_fini(world); +} + +void Query_cascade_desc_topological(void) { + ecs_world_t *world = ecs_init(); + + ECS_ENTITY(world, R, Traversable); + ECS_TAG(world, Tag); + + ecs_entity_t e1 = ecs_new(world, Tag); + ecs_entity_t e2 = ecs_new(world, Tag); + ecs_entity_t e3 = ecs_new(world, Tag); + ecs_entity_t e4 = ecs_new(world, Tag); + ecs_entity_t e5 = ecs_new(world, Tag); + ecs_entity_t e6 = ecs_new(world, Tag); + + ecs_add_pair(world, e3, R, e1); + ecs_add_pair(world, e3, R, e2); + ecs_add_pair(world, e3, R, e4); + ecs_add_pair(world, e1, R, e5); + ecs_add_pair(world, e2, R, e6); + ecs_add_pair(world, e4, R, e1); + ecs_add_pair(world, e4, R, e2); + + ecs_query_t *q = ecs_query_new(world, "Tag, ?Tag(cascade|desc(R))"); + 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(it.entities[0], e3); + + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + test_uint(it.entities[0], e4); + + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + test_uint(it.entities[0], e1); + + test_bool(true, ecs_query_next(&it)); + test_int(1, it.count); + test_uint(it.entities[0], e2); + + test_bool(true, ecs_query_next(&it)); + test_int(2, it.count); + test_uint(it.entities[0], e5); + test_uint(it.entities[1], e6); + test_bool(false, ecs_query_next(&it)); + + ecs_fini(world); +} + void Query_childof_rematch_from_isa(void) { ecs_world_t *world = ecs_mini(); diff --git a/test/api/src/main.c b/test/api/src/main.c index aeed4eaf6..9cbe3113f 100644 --- a/test/api/src/main.c +++ b/test/api/src/main.c @@ -1535,6 +1535,8 @@ void Query_existing_custom_rel_cascade(void); void Query_new_custom_rel_cascade(void); void Query_cascade_w_2_depths(void); void Query_cascade_w_3_depths(void); +void Query_cascade_w_2_depths_desc(void); +void Query_cascade_w_3_depths_desc(void); void Query_not_pair_relation_wildcard(void); void Query_not_pair_object_wildcard(void); void Query_two_pair_wildcards_one_not(void); @@ -1567,6 +1569,9 @@ void Query_childof_rematch_2_lvls(void); void Query_cascade_rematch_2_lvls(void); void Query_cascade_rematch_2_lvls_2_relations(void); void Query_cascade_topological(void); +void Query_cascade_desc_rematch_2_lvls(void); +void Query_cascade_desc_rematch_2_lvls_2_relations(void); +void Query_cascade_desc_topological(void); void Query_childof_rematch_from_isa(void); void Query_rematch_optional_ref(void); void Query_rematch_optional_ref_w_2_refs(void); @@ -8565,6 +8570,14 @@ bake_test_case Query_testcases[] = { "cascade_w_3_depths", Query_cascade_w_3_depths }, + { + "cascade_w_2_depths_desc", + Query_cascade_w_2_depths_desc + }, + { + "cascade_w_3_depths_desc", + Query_cascade_w_3_depths_desc + }, { "not_pair_relation_wildcard", Query_not_pair_relation_wildcard @@ -8693,6 +8706,18 @@ bake_test_case Query_testcases[] = { "cascade_topological", Query_cascade_topological }, + { + "cascade_desc_rematch_2_lvls", + Query_cascade_desc_rematch_2_lvls + }, + { + "cascade_desc_rematch_2_lvls_2_relations", + Query_cascade_desc_rematch_2_lvls_2_relations + }, + { + "cascade_desc_topological", + Query_cascade_desc_topological + }, { "childof_rematch_from_isa", Query_childof_rematch_from_isa @@ -13026,7 +13051,7 @@ static bake_test_suite suites[] = { "Query", NULL, NULL, - 235, + 240, Query_testcases }, { diff --git a/test/cpp_api/project.json b/test/cpp_api/project.json index e24fc191c..3c2145c77 100644 --- a/test/cpp_api/project.json +++ b/test/cpp_api/project.json @@ -668,6 +668,7 @@ "cascade_w_relationship", "up_w_type", "cascade_w_type", + "cascade_desc", "named_query", "term_w_write", "term_w_read", diff --git a/test/cpp_api/src/QueryBuilder.cpp b/test/cpp_api/src/QueryBuilder.cpp index 9870cc26e..b7b54bb3e 100644 --- a/test/cpp_api/src/QueryBuilder.cpp +++ b/test/cpp_api/src/QueryBuilder.cpp @@ -1819,6 +1819,59 @@ void QueryBuilder_cascade(void) { test_int(count, 3); } +void QueryBuilder_cascade_desc(void) { + flecs::world ecs; + + auto Tag = ecs.entity(); + auto Foo = ecs.entity(); + auto Bar = ecs.entity(); + + auto e0 = ecs.entity().add(Tag); + auto e1 = ecs.entity().is_a(e0); + auto e2 = ecs.entity().is_a(e1); + auto e3 = ecs.entity().is_a(e2); + + auto q = ecs.query_builder() + .term(Tag).cascade().desc() + .build(); + + e1.add(Bar); + e2.add(Foo); + + bool e1_found = false; + bool e2_found = false; + bool e3_found = false; + + int32_t count = 0; + q.each([&](flecs::entity e) { + count ++; + + if (e == e1) { + test_bool(e1_found, false); + test_bool(e2_found, true); + test_bool(e3_found, true); + e1_found = true; + } + if (e == e2) { + test_bool(e1_found, false); + test_bool(e2_found, false); + test_bool(e3_found, true); + e2_found = true; + } + if (e == e3) { + test_bool(e1_found, false); + test_bool(e2_found, false); + test_bool(e3_found, false); + e3_found = true; + } + }); + + test_bool(e1_found, true); + test_bool(e2_found, true); + test_bool(e3_found, true); + test_int(count, 3); +} + void QueryBuilder_cascade_w_relationship(void) { flecs::world ecs; diff --git a/test/cpp_api/src/main.cpp b/test/cpp_api/src/main.cpp index da25cc938..f9504ac36 100644 --- a/test/cpp_api/src/main.cpp +++ b/test/cpp_api/src/main.cpp @@ -642,6 +642,7 @@ void QueryBuilder_cascade(void); void QueryBuilder_cascade_w_relationship(void); void QueryBuilder_up_w_type(void); void QueryBuilder_cascade_w_type(void); +void QueryBuilder_cascade_desc(void); void QueryBuilder_named_query(void); void QueryBuilder_term_w_write(void); void QueryBuilder_term_w_read(void); @@ -3800,6 +3801,10 @@ bake_test_case QueryBuilder_testcases[] = { "cascade_w_type", QueryBuilder_cascade_w_type }, + { + "cascade_desc", + QueryBuilder_cascade_desc + }, { "named_query", QueryBuilder_named_query @@ -6484,7 +6489,7 @@ static bake_test_suite suites[] = { "QueryBuilder", NULL, NULL, - 68, + 69, QueryBuilder_testcases }, { diff --git a/test/custom_builds/c/define_debug_and_sanitize/src/main.c b/test/custom_builds/c/define_debug_and_sanitize/src/main.c index 83e864b2b..bb1022671 100644 --- a/test/custom_builds/c/define_debug_and_sanitize/src/main.c +++ b/test/custom_builds/c/define_debug_and_sanitize/src/main.c @@ -3,14 +3,5 @@ int main(int argc, char *argv[]) { ecs_world_t *world = ecs_init_w_args(argc, argv); - - /* Set target FPS for main loop */ - ecs_set_target_fps(world, 60); - - printf("Application define_debug_and_sanitize is running, press CTRL-C to exit...\n"); - - /* Run systems */ - while ( ecs_progress(world, 0)); - return ecs_fini(world); }