From 36d05727e2e216f2a38f1dca3604f04d9e266e04 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Sat, 8 Jan 2022 02:02:23 -0800 Subject: [PATCH] #575 add ability to access flecs::iter from each, add more C++ query examples --- .../ad_hoc_query/include/ad_hoc_query.h | 16 +++ .../include/ad_hoc_query/bake_config.h | 24 +++++ .../cpp/queries/ad_hoc_query/project.json | 11 +++ .../cpp/queries/ad_hoc_query/src/main.cpp | 37 +++++++ examples/cpp/queries/basics/src/main.cpp | 34 ++++++- .../queries/instancing/include/instancing.h | 16 +++ .../include/instancing/bake_config.h | 24 +++++ examples/cpp/queries/instancing/project.json | 11 +++ examples/cpp/queries/instancing/src/main.cpp | 97 +++++++++++++++++++ examples/cpp/queries/iter/include/iter.h | 16 +++ .../queries/iter/include/iter/bake_config.h | 24 +++++ examples/cpp/queries/iter/project.json | 11 +++ examples/cpp/queries/iter/src/main.cpp | 64 ++++++++++++ .../cpp/queries/sorting/include/sorting.h | 16 +++ .../sorting/include/sorting/bake_config.h | 24 +++++ examples/cpp/queries/sorting/project.json | 11 +++ examples/cpp/queries/sorting/src/main.cpp | 66 +++++++++++++ flecs.h | 59 +++++++++-- include/flecs/addons/cpp/impl/iter.hpp | 4 +- include/flecs/addons/cpp/invoker.hpp | 41 +++++++- include/flecs/addons/cpp/iter.hpp | 6 +- include/flecs/addons/cpp/utils/iterable.hpp | 8 +- test/cpp_api/project.json | 3 +- test/cpp_api/src/Query.cpp | 30 ++++++ test/cpp_api/src/main.cpp | 7 +- 25 files changed, 637 insertions(+), 23 deletions(-) create mode 100644 examples/cpp/queries/ad_hoc_query/include/ad_hoc_query.h create mode 100644 examples/cpp/queries/ad_hoc_query/include/ad_hoc_query/bake_config.h create mode 100644 examples/cpp/queries/ad_hoc_query/project.json create mode 100644 examples/cpp/queries/ad_hoc_query/src/main.cpp create mode 100644 examples/cpp/queries/instancing/include/instancing.h create mode 100644 examples/cpp/queries/instancing/include/instancing/bake_config.h create mode 100644 examples/cpp/queries/instancing/project.json create mode 100644 examples/cpp/queries/instancing/src/main.cpp create mode 100644 examples/cpp/queries/iter/include/iter.h create mode 100644 examples/cpp/queries/iter/include/iter/bake_config.h create mode 100644 examples/cpp/queries/iter/project.json create mode 100644 examples/cpp/queries/iter/src/main.cpp create mode 100644 examples/cpp/queries/sorting/include/sorting.h create mode 100644 examples/cpp/queries/sorting/include/sorting/bake_config.h create mode 100644 examples/cpp/queries/sorting/project.json create mode 100644 examples/cpp/queries/sorting/src/main.cpp diff --git a/examples/cpp/queries/ad_hoc_query/include/ad_hoc_query.h b/examples/cpp/queries/ad_hoc_query/include/ad_hoc_query.h new file mode 100644 index 000000000..b812a9b28 --- /dev/null +++ b/examples/cpp/queries/ad_hoc_query/include/ad_hoc_query.h @@ -0,0 +1,16 @@ +#ifndef AD_HOC_QUERY_H +#define AD_HOC_QUERY_H + +/* This generated file contains includes for project dependencies */ +#include "ad_hoc_query/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/cpp/queries/ad_hoc_query/include/ad_hoc_query/bake_config.h b/examples/cpp/queries/ad_hoc_query/include/ad_hoc_query/bake_config.h new file mode 100644 index 000000000..d34bd1e2a --- /dev/null +++ b/examples/cpp/queries/ad_hoc_query/include/ad_hoc_query/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 AD_HOC_QUERY_BAKE_CONFIG_H +#define AD_HOC_QUERY_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/cpp/queries/ad_hoc_query/project.json b/examples/cpp/queries/ad_hoc_query/project.json new file mode 100644 index 000000000..c13acfdd8 --- /dev/null +++ b/examples/cpp/queries/ad_hoc_query/project.json @@ -0,0 +1,11 @@ +{ + "id": "ad_hoc_query", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "language": "c++", + "public": false + } +} \ No newline at end of file diff --git a/examples/cpp/queries/ad_hoc_query/src/main.cpp b/examples/cpp/queries/ad_hoc_query/src/main.cpp new file mode 100644 index 000000000..5f7665cc7 --- /dev/null +++ b/examples/cpp/queries/ad_hoc_query/src/main.cpp @@ -0,0 +1,37 @@ +#include +#include + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int, char *[]) { + flecs::world ecs; + + // Create a few test entities for a Position, Velocity query + ecs.entity("e1") + .set({10, 20}) + .set({1, 2}); + + ecs.entity("e2") + .set({10, 20}) + .set({3, 4}); + + // This entity will not match as it does not have Position, Velocity + ecs.entity("e3") + .set({10, 20}); + + // Ad hoc queries are bit slower to iterate than flecs::query, but are + // faster to create, and in most cases require no allocations. Under the + // hood this API uses flecs::filter, which can be used directly for more + // complex queries. + ecs.each([](flecs::entity e, Position& p, Velocity& v) { + p.x += v.x; + p.y += v.y; + std::cout << e.name() << ": {" << p.x << ", " << p.y << "}\n"; + }); +} diff --git a/examples/cpp/queries/basics/src/main.cpp b/examples/cpp/queries/basics/src/main.cpp index 42c8b0911..27192ce7c 100644 --- a/examples/cpp/queries/basics/src/main.cpp +++ b/examples/cpp/queries/basics/src/main.cpp @@ -29,18 +29,42 @@ int main(int, char *[]) { ecs.entity("e3") .set({10, 20}); - // Iterate entities matching the query + + // The next lines show the different ways in which a query can be iterated. + // Note how the 'const' qualifier matches the query template arguments. + + // The each() function iterates each entity individually and accepts an + // entity argument plus arguments for each query component: q.each([](flecs::entity e, Position& p, const Velocity& v) { p.x += v.x; p.y += v.y; std::cout << e.name() << ": {" << p.x << ", " << p.y << "}\n"; }); - // Ad hoc queries are bit slower to iterate than queries, but are faster to - // create as they don't require setting up a cache. - ecs.each([](flecs::entity e, Position& p, Velocity& v) { + // You can omit the flecs::entity argument if it's not needed: + q.each([](Position& p, const Velocity& v) { p.x += v.x; p.y += v.y; - std::cout << e.name() << ": {" << p.x << ", " << p.y << "}\n"; + std::cout << "{" << p.x << ", " << p.y << "}\n"; + }); + + // Each also accepts flecs::iter + index (for the iterated entity) arguemnts + // currently being iterated. A flecs::iter has lots of information on what + // is being iterated, which is demonstrated in the "iter" example. + q.each([](flecs::iter& it, size_t i, Position& p, const Velocity& v) { + p.x += v.x; + p.y += v.y; + std::cout << it.entity(i).name() << ": {" << p.x << ", " << p.y << "}\n"; + }); + + // Iter is a bit more verbose, but allows for more control over how entities + // are iterated as it provides multiple entities in the same callback. + q.iter([](flecs::iter& it, Position *p, const Velocity *v) { + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + std::cout << it.entity(i).name() << + ": {" << p[i].x << ", " << p[i].y << "}\n"; + } }); } diff --git a/examples/cpp/queries/instancing/include/instancing.h b/examples/cpp/queries/instancing/include/instancing.h new file mode 100644 index 000000000..e1620a5bb --- /dev/null +++ b/examples/cpp/queries/instancing/include/instancing.h @@ -0,0 +1,16 @@ +#ifndef INSTANCING_H +#define INSTANCING_H + +/* This generated file contains includes for project dependencies */ +#include "instancing/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/cpp/queries/instancing/include/instancing/bake_config.h b/examples/cpp/queries/instancing/include/instancing/bake_config.h new file mode 100644 index 000000000..f1866b26b --- /dev/null +++ b/examples/cpp/queries/instancing/include/instancing/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 INSTANCING_BAKE_CONFIG_H +#define INSTANCING_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/cpp/queries/instancing/project.json b/examples/cpp/queries/instancing/project.json new file mode 100644 index 000000000..c57e07476 --- /dev/null +++ b/examples/cpp/queries/instancing/project.json @@ -0,0 +1,11 @@ +{ + "id": "instancing", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "language": "c++", + "public": false + } +} \ No newline at end of file diff --git a/examples/cpp/queries/instancing/src/main.cpp b/examples/cpp/queries/instancing/src/main.cpp new file mode 100644 index 000000000..4feb10e3c --- /dev/null +++ b/examples/cpp/queries/instancing/src/main.cpp @@ -0,0 +1,97 @@ +#include +#include + +/* Instancing is the ability of queries to iterate results with fields that have + * different numbers of elements. The term "instancing" is borrowed from + * graphics APIs, where it means reusing the same data for multiple "instances". + * + * Query instancing works in much the same way. By default queries match all + * components on the same entity. It is however possible to request data from + * other entities, like getting the Position from the entity's parent. + * + * Instancing refers to the ability of queries to iterate components for + * multiple entities while at the same time providing "instanced" components, + * which are always provided one element at a time. + * + * Instancing is often used in combination with parent-child relationships and + * prefabs, but is applicable to any kind of query where some of the terms are + * matched on N entities, and some on a single entity. + * + * By default queries are not instanced, which means that if a result contains + * mixed fields, entities will be iterated one by one instead of in batches. + * This is safer, as code doesn't have to do anything different for owned and + * shared fields, but does come at a performance penalty. + * + * The each() iterator function always uses an instanced iterator under the + * hood. This is transparent to the application, but improves performance. For + * this reason using each() can be faster than using uninstanced iter(). + */ + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +int main(int, char *[]) { + flecs::world ecs; + + // Create a query for Position, Velocity. We'll create a few entities that + // have Velocity as owned and shared component. + auto q = ecs.query_builder() + .arg(1).set(flecs::Self) // Ensure Position is never shared + .instanced() // create instanced query + .build(); + + // Create a prefab with Velocity. Prefabs are not matched with queries. + auto prefab = ecs.prefab("p") + .set({1, 2}); + + // Create a few entities that own Position & share Velocity from the prefab. + ecs.entity("e1").is_a(prefab) + .set({10, 20}); + + ecs.entity("e2").is_a(prefab) + .set({10, 20}); + + // Create a few entities that own all components + ecs.entity("e3") + .set({10, 20}) + .set({3, 4}); + + ecs.entity("e4") + .set({10, 20}) + .set({3, 4}); + + + // Iterate the instanced query. Note how when a query is instanced, it needs + // to check whether a field is owned or not in order to know how to access + // it. In the case of an owned field it is iterated as an array, whereas + // in the case of a shared field, it is accessed as a pointer. + q.iter([](flecs::iter& it, Position *p, const Velocity *v) { + + // Check if Velocity is owned, in which case it's accessed as array. + // Position will always be owned, since we set the term to Self. + if (it.is_owned(2)) { // Velocity is term 2 + std::cout << "Velocity is owned" << std::endl; + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + std::cout << it.entity(i).name() << + ": {" << p[i].x << ", " << p[i].y << "}\n"; + } + + // If Velocity is shared, access the field as a pointer. + } else { + std::cout << "Velocity is shared" << std::endl; + for (auto i : it) { + p[i].x += v->x; + p[i].y += v->y; + std::cout << it.entity(i).name() << + ": {" << p[i].x << ", " << p[i].y << "}\n"; + } + } + }); +} diff --git a/examples/cpp/queries/iter/include/iter.h b/examples/cpp/queries/iter/include/iter.h new file mode 100644 index 000000000..3e829472e --- /dev/null +++ b/examples/cpp/queries/iter/include/iter.h @@ -0,0 +1,16 @@ +#ifndef ITER_H +#define ITER_H + +/* This generated file contains includes for project dependencies */ +#include "iter/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/cpp/queries/iter/include/iter/bake_config.h b/examples/cpp/queries/iter/include/iter/bake_config.h new file mode 100644 index 000000000..4852734b6 --- /dev/null +++ b/examples/cpp/queries/iter/include/iter/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 ITER_BAKE_CONFIG_H +#define ITER_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/cpp/queries/iter/project.json b/examples/cpp/queries/iter/project.json new file mode 100644 index 000000000..4be968041 --- /dev/null +++ b/examples/cpp/queries/iter/project.json @@ -0,0 +1,11 @@ +{ + "id": "iter", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "language": "c++", + "public": false + } +} diff --git a/examples/cpp/queries/iter/src/main.cpp b/examples/cpp/queries/iter/src/main.cpp new file mode 100644 index 000000000..ac9964c95 --- /dev/null +++ b/examples/cpp/queries/iter/src/main.cpp @@ -0,0 +1,64 @@ +#include +#include + +struct Position { + double x, y; +}; + +struct Velocity { + double x, y; +}; + +struct Mass { + double value; +}; + +int main(int, char *[]) { + flecs::world ecs; + + // Create a query for Position, Velocity. + auto q = ecs.query(); + + // Create a few test entities for a Position, Velocity query + ecs.entity("e1") + .set({10, 20}) + .set({1, 2}); + + ecs.entity("e2") + .set({10, 20}) + .set({3, 4}); + + ecs.entity("e3") + .set({10, 20}) + .set({4, 5}) + .set({50}); + + // The iter function provides a flecs::iter object which contains all sorts + // of information on the entities currently being iterated. + // The function passed to iter is by default called for each table the query + // is matched with. + q.iter([&](flecs::iter& it, Position *p, const Velocity *v) { + // Print the table & number of entities matched in current callback + std::cout << "Table [" << it.type().str() << "]" << std::endl; + std::cout << " - number of entities: " << it.count() << std::endl; + + // Print information about the components being matched + for (int i = 1; i <= it.term_count(); i ++) { + std::cout << " - term " << i << ": " << std::endl; + std::cout << " - component: " << it.id(i).str() << std::endl; + std::cout << " - type size: " << it.size(i) << std::endl; + } + + std::cout << std::endl; + + // Iterate entities + for (auto i : it) { + p[i].x += v[i].x; + p[i].y += v[i].y; + std::cout << " - " << it.entity(i).name() << + ": {" << p[i].x << ", " << p[i].y << "}\n"; + } + + std::cout << std::endl; + }); +} diff --git a/examples/cpp/queries/sorting/include/sorting.h b/examples/cpp/queries/sorting/include/sorting.h new file mode 100644 index 000000000..e509299d4 --- /dev/null +++ b/examples/cpp/queries/sorting/include/sorting.h @@ -0,0 +1,16 @@ +#ifndef SORTING_H +#define SORTING_H + +/* This generated file contains includes for project dependencies */ +#include "sorting/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/cpp/queries/sorting/include/sorting/bake_config.h b/examples/cpp/queries/sorting/include/sorting/bake_config.h new file mode 100644 index 000000000..5b9002713 --- /dev/null +++ b/examples/cpp/queries/sorting/include/sorting/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 SORTING_BAKE_CONFIG_H +#define SORTING_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/cpp/queries/sorting/project.json b/examples/cpp/queries/sorting/project.json new file mode 100644 index 000000000..6e35bdd4a --- /dev/null +++ b/examples/cpp/queries/sorting/project.json @@ -0,0 +1,11 @@ +{ + "id": "sorting", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "language": "c++", + "public": false + } +} \ No newline at end of file diff --git a/examples/cpp/queries/sorting/src/main.cpp b/examples/cpp/queries/sorting/src/main.cpp new file mode 100644 index 000000000..e5fee5ecf --- /dev/null +++ b/examples/cpp/queries/sorting/src/main.cpp @@ -0,0 +1,66 @@ +#include +#include + +struct Position { + double x, y; +}; + +// Order by x member of Position */ +int compare_position( + flecs::entity_t e1, + const Position *p1, + flecs::entity_t e2, + const Position *p2) +{ + (void)e1; + (void)e2; + return (p1->x > p2->x) - (p1->x < p2->x); +} + +// Iterate query, printed values will be ordered +void print_query(flecs::query& q) { + q.each([](flecs::entity, Position& p) { + std::cout << "{" << p.x << "," << p.y << "}" << std::endl; + }); +} + +int main(int argc, char *argv[]) { + flecs::world ecs(argc, argv); + + // Create entities, set Position in random order + auto e = ecs.entity().set({1, 0}); + ecs.entity().set({6, 0}); + ecs.entity().set({2, 0}); + ecs.entity().set({5, 0}); + ecs.entity().set({4, 0}); + + // Create a sorted system + auto sys = ecs.system() + .order_by(compare_position) + .each([](Position &p) { + std::cout << "{" << p.x << "," << p.y << "}" << std::endl; + }); + + // Create a sorted query + auto q = ecs.query_builder() + .order_by(compare_position) + .build(); + + // Iterate query, print values of Position + std::cout << "-- First iteration" << std::endl; + print_query(q); + + // Change the value of one entity, invalidating the order + e.set({7, 0}); + + // Iterate query again, printed values are still ordered + std::cout << "-- Second iteration" << std::endl; + print_query(q); + + // Create new entity to show that data is also sorted for system + ecs.entity().set({3, 0}); + + // Run system, output will be sorted + std::cout << "-- System iteration" << std::endl; + sys.run(); +} diff --git a/flecs.h b/flecs.h index 6654715a1..93a9e12f1 100644 --- a/flecs.h +++ b/flecs.h @@ -13716,7 +13716,7 @@ struct iter { * * @param index The term id. */ - size_t term_size(int32_t index) const { + size_t size(int32_t index) const { return ecs_term_size(m_iter, index); } @@ -13724,13 +13724,13 @@ struct iter { * * @param index The term index. */ - flecs::entity term_source(int32_t index) const; + flecs::entity source(int32_t index) const; /** Obtain component/tag entity of term. * * @param index The term index. */ - flecs::entity term_id(int32_t index) const; + flecs::entity id(int32_t index) const; /** Obtain term with const type. * If the specified term index does not match with the provided type, the @@ -15477,7 +15477,12 @@ struct each_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) == (arity>::value - 1); + (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"); @@ -15548,10 +15553,42 @@ struct each_invoker : public invoker { #endif } + + // Number of function arguments is two more than number of components, pass + // iter + index as argument. + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && PassIter> = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { +#ifndef NDEBUG + ecs_table_t *table = iter->table; + if (table) { + ecs_table_lock(iter->world, table); + } +#endif + + size_t count = static_cast(iter->count); + flecs::iter it(iter); + + for (size_t i = 0; i < count; i ++) { + func(it, i, (ColumnType< remove_reference_t >(comps, i) + .get_row())...); + } + +#ifndef NDEBUG + if (table) { + ecs_table_unlock(iter->world, table); + } +#endif + } + + // Number of function arguments is equal to number of components, no entity template class ColumnType, typename... Args, if_t< - sizeof...(Components) == sizeof...(Args) && !PassEntity> = 0> + sizeof...(Components) == sizeof...(Args) && !PassEntity && !PassIter> = 0> static void invoke_callback( ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) { @@ -15869,6 +15906,7 @@ struct iterable { * The "each" iterator accepts a function that is invoked for each matching * entity. The following function signatures are valid: * - func(flecs::entity e, Components& ...) + * - func(flecs::iter& it, int32_t index, Components& ....) * - func(Components& ...) * * Each iterators are automatically instanced. @@ -15882,8 +15920,13 @@ struct iterable { /** Iter iterator. * The "iter" iterator accepts a function that is invoked for each matching * table. The following function signatures are valid: - * - func(flecs::entity e, Components& ...) + * - func(flecs::iter& it, Components* ...) * - func(Components& ...) + * + * Iter iterators are not automatically instanced. When a result contains + * shared components, entities of the result will be iterated one by one. + * This ensures that applications can't accidentally read out of bounds by + * accessing a shared component as an array. */ template void iter(Func&& func) const { @@ -19421,11 +19464,11 @@ inline column::column(iter &iter, int32_t index) { *this = iter.term(index); } -inline flecs::entity iter::term_source(int32_t index) const { +inline flecs::entity iter::source(int32_t index) const { return flecs::entity(m_iter->world, ecs_term_source(m_iter, index)); } -inline flecs::entity iter::term_id(int32_t index) const { +inline flecs::entity iter::id(int32_t index) const { return flecs::entity(m_iter->world, ecs_term_id(m_iter, index)); } diff --git a/include/flecs/addons/cpp/impl/iter.hpp b/include/flecs/addons/cpp/impl/iter.hpp index bf5272b55..d7e243899 100644 --- a/include/flecs/addons/cpp/impl/iter.hpp +++ b/include/flecs/addons/cpp/impl/iter.hpp @@ -32,11 +32,11 @@ inline column::column(iter &iter, int32_t index) { *this = iter.term(index); } -inline flecs::entity iter::term_source(int32_t index) const { +inline flecs::entity iter::source(int32_t index) const { return flecs::entity(m_iter->world, ecs_term_source(m_iter, index)); } -inline flecs::entity iter::term_id(int32_t index) const { +inline flecs::entity iter::id(int32_t index) const { return flecs::entity(m_iter->world, ecs_term_id(m_iter, index)); } diff --git a/include/flecs/addons/cpp/invoker.hpp b/include/flecs/addons/cpp/invoker.hpp index 8f8a26ef1..06270e013 100644 --- a/include/flecs/addons/cpp/invoker.hpp +++ b/include/flecs/addons/cpp/invoker.hpp @@ -130,7 +130,12 @@ struct each_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) == (arity>::value - 1); + (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"); @@ -201,10 +206,42 @@ struct each_invoker : public invoker { #endif } + + // Number of function arguments is two more than number of components, pass + // iter + index as argument. + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args) && PassIter> = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { +#ifndef NDEBUG + ecs_table_t *table = iter->table; + if (table) { + ecs_table_lock(iter->world, table); + } +#endif + + size_t count = static_cast(iter->count); + flecs::iter it(iter); + + for (size_t i = 0; i < count; i ++) { + func(it, i, (ColumnType< remove_reference_t >(comps, i) + .get_row())...); + } + +#ifndef NDEBUG + if (table) { + ecs_table_unlock(iter->world, table); + } +#endif + } + + // Number of function arguments is equal to number of components, no entity template class ColumnType, typename... Args, if_t< - sizeof...(Components) == sizeof...(Args) && !PassEntity> = 0> + sizeof...(Components) == sizeof...(Args) && !PassEntity && !PassIter> = 0> static void invoke_callback( ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) { diff --git a/include/flecs/addons/cpp/iter.hpp b/include/flecs/addons/cpp/iter.hpp index b44e250f7..67190689a 100644 --- a/include/flecs/addons/cpp/iter.hpp +++ b/include/flecs/addons/cpp/iter.hpp @@ -268,7 +268,7 @@ struct iter { * * @param index The term id. */ - size_t term_size(int32_t index) const { + size_t size(int32_t index) const { return ecs_term_size(m_iter, index); } @@ -276,13 +276,13 @@ struct iter { * * @param index The term index. */ - flecs::entity term_source(int32_t index) const; + flecs::entity source(int32_t index) const; /** Obtain component/tag entity of term. * * @param index The term index. */ - flecs::entity term_id(int32_t index) const; + flecs::entity id(int32_t index) const; /** Obtain term with const type. * If the specified term index does not match with the provided type, the diff --git a/include/flecs/addons/cpp/utils/iterable.hpp b/include/flecs/addons/cpp/utils/iterable.hpp index ca8064210..b0ea7c1cc 100644 --- a/include/flecs/addons/cpp/utils/iterable.hpp +++ b/include/flecs/addons/cpp/utils/iterable.hpp @@ -13,6 +13,7 @@ struct iterable { * The "each" iterator accepts a function that is invoked for each matching * entity. The following function signatures are valid: * - func(flecs::entity e, Components& ...) + * - func(flecs::iter& it, int32_t index, Components& ....) * - func(Components& ...) * * Each iterators are automatically instanced. @@ -26,8 +27,13 @@ struct iterable { /** Iter iterator. * The "iter" iterator accepts a function that is invoked for each matching * table. The following function signatures are valid: - * - func(flecs::entity e, Components& ...) + * - func(flecs::iter& it, Components* ...) * - func(Components& ...) + * + * Iter iterators are not automatically instanced. When a result contains + * shared components, entities of the result will be iterated one by one. + * This ensures that applications can't accidentally read out of bounds by + * accessing a shared component as an array. */ template void iter(Func&& func) const { diff --git a/test/cpp_api/project.json b/test/cpp_api/project.json index d49141d0c..b28ce7876 100644 --- a/test/cpp_api/project.json +++ b/test/cpp_api/project.json @@ -436,7 +436,8 @@ "query_each_w_func_ptr", "query_iter_w_func_ptr", "query_each_w_func_no_ptr", - "query_iter_w_func_no_ptr" + "query_iter_w_func_no_ptr", + "query_each_w_iter" ] }, { "id": "QueryBuilder", diff --git a/test/cpp_api/src/Query.cpp b/test/cpp_api/src/Query.cpp index 04ec76a88..ffe44fa45 100644 --- a/test/cpp_api/src/Query.cpp +++ b/test/cpp_api/src/Query.cpp @@ -1795,3 +1795,33 @@ void Query_query_iter_w_func_no_ptr() { test_int(ptr->x, 11); test_int(ptr->y, 21); } + +void Query_query_each_w_iter() { + flecs::world w; + + auto e1 = w.entity(); e1.set({e1}); + e1.set({10, 20}); + auto e2 = w.entity(); e2.set({e2}); + e2.set({20, 30}); + + auto q = w.query(); + + int32_t invoked = 0; + q.each([&](flecs::iter& it, int32_t i, Self& s, Position& p) { + test_int(it.count(), 2); + test_int(it.entity(i), s.value); + p.x ++; + p.y ++; + invoked ++; + }); + + test_int(invoked, 2); + + const Position *ptr = e1.get(); + test_int(ptr->x, 11); + test_int(ptr->y, 21); + + ptr = e2.get(); + test_int(ptr->x, 21); + test_int(ptr->y, 31); +} diff --git a/test/cpp_api/src/main.cpp b/test/cpp_api/src/main.cpp index 82c9acf2d..ed43445bf 100644 --- a/test/cpp_api/src/main.cpp +++ b/test/cpp_api/src/main.cpp @@ -413,6 +413,7 @@ void Query_query_each_w_func_ptr(void); void Query_query_iter_w_func_ptr(void); void Query_query_each_w_func_no_ptr(void); void Query_query_iter_w_func_no_ptr(void); +void Query_query_each_w_iter(void); // Testsuite 'QueryBuilder' void QueryBuilder_builder_assign_same_type(void); @@ -2347,6 +2348,10 @@ bake_test_case Query_testcases[] = { { "query_iter_w_func_no_ptr", Query_query_iter_w_func_no_ptr + }, + { + "query_each_w_iter", + Query_query_each_w_iter } }; @@ -3792,7 +3797,7 @@ static bake_test_suite suites[] = { "Query", NULL, NULL, - 60, + 61, Query_testcases }, {