From 60a3a11d4c078996e2b072db592f10b4b05d4c46 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Mon, 18 Sep 2023 18:56:34 -0700 Subject: [PATCH] Implement iterable::find method for --- .../queries/find_entity/include/find_entity.h | 16 ++ .../include/find_entity/bake_config.h | 24 +++ examples/cpp/queries/find_entity/project.json | 11 ++ examples/cpp/queries/find_entity/src/main.cpp | 30 ++++ flecs.h | 151 +++++++++++++++++ include/flecs/addons/cpp/invoker.hpp | 153 ++++++++++++++++++ include/flecs/addons/cpp/utils/iterable.hpp | 23 +++ test/cpp_api/project.json | 15 +- test/cpp_api/src/Filter.cpp | 46 ++++++ test/cpp_api/src/Query.cpp | 46 ++++++ test/cpp_api/src/RuleBuilder.cpp | 46 ++++++ test/cpp_api/src/main.cpp | 51 +++++- 12 files changed, 606 insertions(+), 6 deletions(-) create mode 100644 examples/cpp/queries/find_entity/include/find_entity.h create mode 100644 examples/cpp/queries/find_entity/include/find_entity/bake_config.h create mode 100644 examples/cpp/queries/find_entity/project.json create mode 100644 examples/cpp/queries/find_entity/src/main.cpp diff --git a/examples/cpp/queries/find_entity/include/find_entity.h b/examples/cpp/queries/find_entity/include/find_entity.h new file mode 100644 index 000000000..2d8cf6dec --- /dev/null +++ b/examples/cpp/queries/find_entity/include/find_entity.h @@ -0,0 +1,16 @@ +#ifndef FIND_ENTITY_H +#define FIND_ENTITY_H + +/* This generated file contains includes for project dependencies */ +#include "find_entity/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/cpp/queries/find_entity/include/find_entity/bake_config.h b/examples/cpp/queries/find_entity/include/find_entity/bake_config.h new file mode 100644 index 000000000..882d4d6c3 --- /dev/null +++ b/examples/cpp/queries/find_entity/include/find_entity/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef FIND_ENTITY_BAKE_CONFIG_H +#define FIND_ENTITY_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/cpp/queries/find_entity/project.json b/examples/cpp/queries/find_entity/project.json new file mode 100644 index 000000000..31ee9b87b --- /dev/null +++ b/examples/cpp/queries/find_entity/project.json @@ -0,0 +1,11 @@ +{ + "id": "find_entity", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "language": "c++", + "public": false + } +} \ No newline at end of file diff --git a/examples/cpp/queries/find_entity/src/main.cpp b/examples/cpp/queries/find_entity/src/main.cpp new file mode 100644 index 000000000..829eff761 --- /dev/null +++ b/examples/cpp/queries/find_entity/src/main.cpp @@ -0,0 +1,30 @@ +#include +#include + +struct Position { + double x, y; +}; + +int main() { + flecs::world ecs; + + ecs.entity("e1").set({10, 20}); + ecs.entity("e2").set({20, 30}); + + // Create a simple query for component Position + flecs::query q = ecs.query(); + + // Find the entity for which Position.x is 20 + flecs::entity e = q.find([](Position& p) { + return p.x == 20.0; + }); + + if (e) { + std::cout << "Found entity " << e.path() << std::endl; + } else { + std::cout << "No entity found" << std::endl; + } + + // Output + // Found entity ::e2 +} diff --git a/flecs.h b/flecs.h index 8104e3478..f5c048cc7 100644 --- a/flecs.h +++ b/flecs.h @@ -24100,6 +24100,151 @@ struct each_invoker : public invoker { Func m_func; }; +template +struct find_invoker : public invoker { + // If the number of arguments in the function signature is one more than the + // number of components in the query, an extra entity arg is required. + static constexpr bool PassEntity = + (sizeof...(Components) + 1) == (arity::value); + + // If the number of arguments in the function is two more than the number of + // components in the query, extra iter + index arguments are required. + static constexpr bool PassIter = + (sizeof...(Components) + 2) == (arity::value); + + static_assert(arity::value > 0, + "each() must have at least one argument"); + + using Terms = typename term_ptrs::array; + + template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> + explicit find_invoker(Func&& func) noexcept + : m_func(FLECS_MOV(func)) { } + + explicit find_invoker(const Func& func) noexcept + : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the invoker, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + term_ptrs terms; + + if (terms.populate(iter)) { + invoke_callback< each_ref_column >(iter, m_func, 0, terms.m_terms); + } else { + invoke_callback< each_column >(iter, m_func, 0, terms.m_terms); + } + } + + // Find invokers always use instanced iterators + static bool instanced() { + return true; + } + +private: + // Number of function arguments is one more than number of components, pass + // entity as argument. + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && PassEntity> = 0> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + ECS_TABLE_LOCK(iter->world, iter->table); + + ecs_world_t *world = iter->world; + size_t count = static_cast(iter->count); + + ecs_assert(count > 0, ECS_INVALID_OPERATION, + "no entities returned, use find() without flecs::entity argument"); + + for (size_t i = 0; i < count; i ++) { + if (func(flecs::entity(world, iter->entities[i]), + (ColumnType< remove_reference_t >(comps, i) + .get_row())...)) + { + return flecs::entity(world, iter->entities[i]); + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + + return flecs::entity(); + } + + // Number of function arguments is two more than number of components, pass + // iter + index as argument. + template class ColumnType, + typename... Args, int Enabled = PassIter, if_t< + sizeof...(Components) == sizeof...(Args) && Enabled> = 0> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + size_t count = static_cast(iter->count); + if (count == 0) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } + + flecs::iter it(iter); + + ECS_TABLE_LOCK(iter->world, iter->table); + + for (size_t i = 0; i < count; i ++) { + if (func(it, i, (ColumnType< remove_reference_t >(comps, i) + .get_row())...)) + { + return flecs::entity(world, iter->entities[i]); + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + + return flecs::entity(); + } + + // Number of function arguments is equal to number of components, no entity + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && !PassEntity && !PassIter> = 0> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + size_t count = static_cast(iter->count); + if (count == 0) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } + + flecs::iter it(iter); + + ECS_TABLE_LOCK(iter->world, iter->table); + + for (size_t i = 0; i < count; i ++) { + if (func( (ColumnType< remove_reference_t >(comps, i) + .get_row())...)) + { + return flecs::entity(world, iter->entities[i]); + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + } + + template class ColumnType, + typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> + static flecs::entity invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Args... comps) + { + return invoke_callback( + iter, func, index + 1, columns, comps..., columns[index]); + } + + Func m_func; +}; //////////////////////////////////////////////////////////////////////////////// //// Utility class to invoke a system iterate action @@ -24480,6 +24625,12 @@ struct iterable { this->next_each_action()); } + template + flecs::entity find(Func&& func) const { + iterate<_::find_invoker>(nullptr, FLECS_FWD(func), + this->next_each_action()); + } + /** Iter iterator. * The "iter" iterator accepts a function that is invoked for each matching * table. The following function signatures are valid: diff --git a/include/flecs/addons/cpp/invoker.hpp b/include/flecs/addons/cpp/invoker.hpp index 3060fc32a..3c8fc0713 100644 --- a/include/flecs/addons/cpp/invoker.hpp +++ b/include/flecs/addons/cpp/invoker.hpp @@ -333,6 +333,159 @@ struct each_invoker : public invoker { Func m_func; }; +template +struct find_invoker : public invoker { + // If the number of arguments in the function signature is one more than the + // number of components in the query, an extra entity arg is required. + static constexpr bool PassEntity = + (sizeof...(Components) + 1) == (arity::value); + + // If the number of arguments in the function is two more than the number of + // components in the query, extra iter + index arguments are required. + static constexpr bool PassIter = + (sizeof...(Components) + 2) == (arity::value); + + static_assert(arity::value > 0, + "each() must have at least one argument"); + + using Terms = typename term_ptrs::array; + + template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> + explicit find_invoker(Func&& func) noexcept + : m_func(FLECS_MOV(func)) { } + + explicit find_invoker(const Func& func) noexcept + : m_func(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the invoker, such as what happens when + // iterating a query. + flecs::entity invoke(ecs_iter_t *iter) const { + term_ptrs terms; + + if (terms.populate(iter)) { + return invoke_callback< each_ref_column >(iter, m_func, 0, terms.m_terms); + } else { + return invoke_callback< each_column >(iter, m_func, 0, terms.m_terms); + } + } + + // Find invokers always use instanced iterators + static bool instanced() { + return true; + } + +private: + // Number of function arguments is one more than number of components, pass + // entity as argument. + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && PassEntity> = 0> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + ECS_TABLE_LOCK(iter->world, iter->table); + + ecs_world_t *world = iter->world; + size_t count = static_cast(iter->count); + flecs::entity result; + + ecs_assert(count > 0, ECS_INVALID_OPERATION, + "no entities returned, use find() without flecs::entity argument"); + + for (size_t i = 0; i < count; i ++) { + if (func(flecs::entity(world, iter->entities[i]), + (ColumnType< remove_reference_t >(comps, i) + .get_row())...)) + { + result = flecs::entity(world, iter->entities[i]); + break; + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + + return result; + } + + // Number of function arguments is two more than number of components, pass + // iter + index as argument. + template class ColumnType, + typename... Args, int Enabled = PassIter, if_t< + sizeof...(Components) == sizeof...(Args) && Enabled> = 0> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + size_t count = static_cast(iter->count); + if (count == 0) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } + + flecs::iter it(iter); + flecs::entity result; + + ECS_TABLE_LOCK(iter->world, iter->table); + + for (size_t i = 0; i < count; i ++) { + if (func(it, i, (ColumnType< remove_reference_t >(comps, i) + .get_row())...)) + { + result = flecs::entity(iter->world, iter->entities[i]); + break; + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + + return result; + } + + // Number of function arguments is equal to number of components, no entity + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && !PassEntity && !PassIter> = 0> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + size_t count = static_cast(iter->count); + if (count == 0) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } + + flecs::iter it(iter); + flecs::entity result; + + ECS_TABLE_LOCK(iter->world, iter->table); + + for (size_t i = 0; i < count; i ++) { + if (func( (ColumnType< remove_reference_t >(comps, i) + .get_row())...)) + { + result = flecs::entity(iter->world, iter->entities[i]); + break; + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + + return result; + } + + template class ColumnType, + typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> + static flecs::entity invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Args... comps) + { + return invoke_callback( + iter, func, index + 1, columns, comps..., columns[index]); + } + + Func m_func; +}; //////////////////////////////////////////////////////////////////////////////// //// Utility class to invoke a system iterate action diff --git a/include/flecs/addons/cpp/utils/iterable.hpp b/include/flecs/addons/cpp/utils/iterable.hpp index 301c74d49..b65d0d235 100644 --- a/include/flecs/addons/cpp/utils/iterable.hpp +++ b/include/flecs/addons/cpp/utils/iterable.hpp @@ -49,6 +49,12 @@ struct iterable { this->next_each_action()); } + template + flecs::entity find(Func&& func) const { + return iterate_find<_::find_invoker>(nullptr, FLECS_FWD(func), + this->next_each_action()); + } + /** Iter iterator. * The "iter" iterator accepts a function that is invoked for each matching * table. The following function signatures are valid: @@ -144,6 +150,23 @@ struct iterable { Invoker(func).invoke(&it); } } + + template < template class Invoker, typename Func, typename NextFunc, typename ... Args> + flecs::entity iterate_find(flecs::world_t *stage, Func&& func, NextFunc next, Args &&... args) const { + ecs_iter_t it = this->get_iter(stage); + if (Invoker::instanced()) { + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + } + + flecs::entity result; + while (!result && next(&it, FLECS_FWD(args)...)) { + result = Invoker(func).invoke(&it); + } + if (result) { + ecs_iter_fini(&it); + } + return result; + } }; template diff --git a/test/cpp_api/project.json b/test/cpp_api/project.json index 35f00f41d..3ea567544 100644 --- a/test/cpp_api/project.json +++ b/test/cpp_api/project.json @@ -590,7 +590,10 @@ "page_iter_captured_query", "worker_iter_captured_query", "iter_entities", - "iter_get_pair_w_id" + "iter_get_pair_w_id", + "find", + "find_not_found", + "find_w_entity" ] }, { "id": "QueryBuilder", @@ -808,7 +811,10 @@ "unresolved_by_name", "scope", "iter_w_stage", - "inspect_terms_w_expr" + "inspect_terms_w_expr", + "find", + "find_not_found", + "find_w_entity" ] }, { "id": "SystemBuilder", @@ -895,7 +901,10 @@ "named_filter", "named_scoped_filter", "set_this_var", - "inspect_terms_w_expr" + "inspect_terms_w_expr", + "find", + "find_not_found", + "find_w_entity" ] }, { "id": "ComponentLifecycle", diff --git a/test/cpp_api/src/Filter.cpp b/test/cpp_api/src/Filter.cpp index 4ab875f3e..0b9ebbf6f 100644 --- a/test/cpp_api/src/Filter.cpp +++ b/test/cpp_api/src/Filter.cpp @@ -472,3 +472,49 @@ void Filter_inspect_terms_w_expr(void) { test_int(count, 1); } + +void Filter_find(void) { + flecs::world ecs; + + /* auto e1 = */ ecs.entity().set({10, 20}); + auto e2 = ecs.entity().set({20, 30}); + + auto q = ecs.filter(); + + auto r = q.find([](Position& p) { + return p.x == 20; + }); + + test_assert(r == e2); +} + +void Filter_find_not_found(void) { + flecs::world ecs; + + /* auto e1 = */ ecs.entity().set({10, 20}); + /* auto e2 = */ ecs.entity().set({20, 30}); + + auto q = ecs.filter(); + + auto r = q.find([](Position& p) { + return p.x == 30; + }); + + test_assert(!r); +} + +void Filter_find_w_entity(void) { + flecs::world ecs; + + /* auto e1 = */ ecs.entity().set({10, 20}).set({20, 30}); + auto e2 = ecs.entity().set({20, 30}).set({20, 30}); + + auto q = ecs.filter(); + + auto r = q.find([](flecs::entity e, Position& p) { + return p.x == e.get()->x && + p.y == e.get()->y; + }); + + test_assert(r == e2); +} diff --git a/test/cpp_api/src/Query.cpp b/test/cpp_api/src/Query.cpp index 4d83412ab..84af6150a 100644 --- a/test/cpp_api/src/Query.cpp +++ b/test/cpp_api/src/Query.cpp @@ -2286,3 +2286,49 @@ void Query_iter_get_pair_w_id(void) { test_int(count, 1); } + +void Query_find(void) { + flecs::world ecs; + + /* auto e1 = */ ecs.entity().set({10, 20}); + auto e2 = ecs.entity().set({20, 30}); + + auto q = ecs.query(); + + auto r = q.find([](Position& p) { + return p.x == 20; + }); + + test_assert(r == e2); +} + +void Query_find_not_found(void) { + flecs::world ecs; + + /* auto e1 = */ ecs.entity().set({10, 20}); + /* auto e2 = */ ecs.entity().set({20, 30}); + + auto q = ecs.query(); + + auto r = q.find([](Position& p) { + return p.x == 30; + }); + + test_assert(!r); +} + +void Query_find_w_entity(void) { + flecs::world ecs; + + /* auto e1 = */ ecs.entity().set({10, 20}).set({20, 30}); + auto e2 = ecs.entity().set({20, 30}).set({20, 30}); + + auto q = ecs.query(); + + auto r = q.find([](flecs::entity e, Position& p) { + return p.x == e.get()->x && + p.y == e.get()->y; + }); + + test_assert(r == e2); +} diff --git a/test/cpp_api/src/RuleBuilder.cpp b/test/cpp_api/src/RuleBuilder.cpp index 4fd5618eb..0c482f5d0 100644 --- a/test/cpp_api/src/RuleBuilder.cpp +++ b/test/cpp_api/src/RuleBuilder.cpp @@ -815,3 +815,49 @@ void RuleBuilder_inspect_terms_w_expr(void) { f.destruct(); } + +void RuleBuilder_find(void) { + flecs::world ecs; + + /* auto e1 = */ ecs.entity().set({10, 20}); + auto e2 = ecs.entity().set({20, 30}); + + auto q = ecs.rule(); + + auto r = q.find([](Position& p) { + return p.x == 20; + }); + + test_assert(r == e2); +} + +void RuleBuilder_find_not_found(void) { + flecs::world ecs; + + /* auto e1 = */ ecs.entity().set({10, 20}); + /* auto e2 = */ ecs.entity().set({20, 30}); + + auto q = ecs.rule(); + + auto r = q.find([](Position& p) { + return p.x == 30; + }); + + test_assert(!r); +} + +void RuleBuilder_find_w_entity(void) { + flecs::world ecs; + + /* auto e1 = */ ecs.entity().set({10, 20}).set({20, 30}); + auto e2 = ecs.entity().set({20, 30}).set({20, 30}); + + auto q = ecs.rule(); + + auto r = q.find([](flecs::entity e, Position& p) { + return p.x == e.get()->x && + p.y == e.get()->y; + }); + + test_assert(r == e2); +} diff --git a/test/cpp_api/src/main.cpp b/test/cpp_api/src/main.cpp index 1a75d8a64..a04a70a5b 100644 --- a/test/cpp_api/src/main.cpp +++ b/test/cpp_api/src/main.cpp @@ -567,6 +567,9 @@ void Query_page_iter_captured_query(void); void Query_worker_iter_captured_query(void); void Query_iter_entities(void); void Query_iter_get_pair_w_id(void); +void Query_find(void); +void Query_find_not_found(void); +void Query_find_w_entity(void); // Testsuite 'QueryBuilder' void QueryBuilder_builder_assign_same_type(void); @@ -779,6 +782,9 @@ void RuleBuilder_unresolved_by_name(void); void RuleBuilder_scope(void); void RuleBuilder_iter_w_stage(void); void RuleBuilder_inspect_terms_w_expr(void); +void RuleBuilder_find(void); +void RuleBuilder_find_not_found(void); +void RuleBuilder_find_w_entity(void); // Testsuite 'SystemBuilder' void SystemBuilder_builder_assign_same_type(void); @@ -860,6 +866,9 @@ void Filter_named_filter(void); void Filter_named_scoped_filter(void); void Filter_set_this_var(void); void Filter_inspect_terms_w_expr(void); +void Filter_find(void); +void Filter_find_not_found(void); +void Filter_find_w_entity(void); // Testsuite 'ComponentLifecycle' void ComponentLifecycle_ctor_on_add(void); @@ -3468,6 +3477,18 @@ bake_test_case Query_testcases[] = { { "iter_get_pair_w_id", Query_iter_get_pair_w_id + }, + { + "find", + Query_find + }, + { + "find_not_found", + Query_find_not_found + }, + { + "find_w_entity", + Query_find_w_entity } }; @@ -4301,6 +4322,18 @@ bake_test_case RuleBuilder_testcases[] = { { "inspect_terms_w_expr", RuleBuilder_inspect_terms_w_expr + }, + { + "find", + RuleBuilder_find + }, + { + "find_not_found", + RuleBuilder_find_not_found + }, + { + "find_w_entity", + RuleBuilder_find_w_entity } }; @@ -4610,6 +4643,18 @@ bake_test_case Filter_testcases[] = { { "inspect_terms_w_expr", Filter_inspect_terms_w_expr + }, + { + "find", + Filter_find + }, + { + "find_not_found", + Filter_find_not_found + }, + { + "find_w_entity", + Filter_find_w_entity } }; @@ -6297,7 +6342,7 @@ static bake_test_suite suites[] = { "Query", NULL, NULL, - 81, + 84, Query_testcases }, { @@ -6318,7 +6363,7 @@ static bake_test_suite suites[] = { "RuleBuilder", NULL, NULL, - 30, + 33, RuleBuilder_testcases }, { @@ -6339,7 +6384,7 @@ static bake_test_suite suites[] = { "Filter", NULL, NULL, - 23, + 26, Filter_testcases }, {