diff --git a/docs/FlecsQueryLanguage.md b/docs/FlecsQueryLanguage.md index 2ee762af7..abbeb9eed 100644 --- a/docs/FlecsQueryLanguage.md +++ b/docs/FlecsQueryLanguage.md @@ -182,7 +182,7 @@ The `any` (`_`) wildcard returns at most one result per wildcard. The following (Likes, _) ``` -If an entity has both `(Likes, Dogs)` and `(Likes, Cats)`, the query will only return one result. The location of the `any` wildcard in the matched id will be replaced with `*`, indicating that no specific pair was matched. The above query would return the following id: +If an entity has both `(Likes, Dogs)` and `(Likes, Cats)`, the query will return only one result. The location of the `any` wildcard in the matched id will be replaced with `*`, indicating that no specific pair was matched. The above query would return the following id: - `(Likes, *)` @@ -239,7 +239,7 @@ Position($this), Velocity($this) Note how both terms have the same `$this` source. Using the same variable ensures that both components are matched on the same entity. -The following expressions show to use pair queries without and with explicit source: +The following expressions show how to use pair queries without and with explicit source: ```swift // Implicit source diff --git a/docs/Queries.md b/docs/Queries.md index 1da89a6ec..e6bed7fbb 100644 --- a/docs/Queries.md +++ b/docs/Queries.md @@ -1,32 +1,25 @@ # Queries -At the core of an Entity Component System are queries, which make it possible to find entities matching a list of conditions in realtime, for example: -``` -Position, Velocity -``` +## Introduction +Queries enable games to quickly find entities that match a list of conditions, and are at the core of many Flecs features like systems, observers, tooling and serialization. -This query returns all entities that at least have the `Position` and `Velocity` components. Queries provide direct access to cache efficient storages of matched components, which gives applications the ability to process large numbers (think millions) of entities each frame. +Flecs queries can do anything from returning entities that match a simple list of components, to matching complex patterns against entity graphs. -## Highlights -Here are some of the highlights of Flecs queries: +This manual contains a full overview of the query features available in Flecs. Some of the features of Flecs queries are: -- Queries can [cache results](#cached-queries), which removes search overhead from time critical game loops +- Queries can be cached, uncached or a mix of both, which lets games pick the ideal balance between iteration performance, query creation performance and administration overhead. -- Queries can efficiently traverse [entity relationship](#Relationships) graphs, reducing the need for building custom hard to maintain data structures ([blog](https://ajmmertens.medium.com/building-games-in-ecs-with-entity-relationships-657275ba2c6c)). - -- Queries can be created at runtime and match components that are created at runtime. +- Queries have advanced features for matching patterns against entity graphs which can be used to build immersive gameplay without having to build and maintain complex custom data structures ([blog](https://ajmmertens.medium.com/building-games-in-ecs-with-entity-relationships-657275ba2c6c)). - Queries support `and`, `or`, `not` and `optional` [operators](#operator-overview). -- Queries can combine components from multiple [sources](#source), like a transform system that uses `Position` component from an entity and its parent. - -- Queries can be observed, allowing applications to get notified when entities start or stop matching a query. +- Queries can combine components from multiple [sources](#source), like matching entities with a `Transform` component and a parent that have a `Transform` component. -- Query results are self describing, which means iterators can be passed to generic code which can then do things like [serializing it to JSON](https://github.com/SanderMertens/flecs/blob/master/include/flecs/addons/json.h). +- Queries can be created at runtime and match components that are created at runtime. -- Queries can be created with the regular API or by parsing a query string, making it possible to [create tools that create queries at runtime](https://www.flecs.dev/explorer/). +- Queries can be created with the Flecs APIs or by using the Flecs Query Language, which enables query creation in [tools like the explorer](https://www.flecs.dev/explorer/). -- Queries support [component inheritance](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/rules/component_inheritance). +- The Flecs REST API has a query endpoint which can be used to build remote game servers. ## Definitions @@ -40,124 +33,142 @@ Here are some of the highlights of Flecs queries: | Target | Used to refer to second element of pair | | Source | Entity on which a term is matched | | Iterator | Object used to iterate a query | +| Term | An element of a query that expresses a condition to match | | Field | A single value or array of values made available by an iterator. An iterator usually provides a field per query term. | ## Examples -Make sure to check out the code examples in the repository: - -https://github.com/SanderMertens/flecs/tree/master/examples +Make sure to check out the query code examples in the repository: - [queries (C)](https://github.com/SanderMertens/flecs/tree/master/examples/c/queries) - [queries (C++)](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/queries) - - [rules (C)](https://github.com/SanderMertens/flecs/tree/master/examples/c/rules) - - [rules (C++)](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/rules) -## Types -Flecs has different query types, which are optimized for different kinds of use cases. This section provides a brief overview of each kind: +## Performance and Caching +Understanding the basic architecture of queries helps to make the right tradeoffs when using queries in games. The biggest impact on query performance is whether a query is cached or not. This section goes over what caching is, how it can be used and when it makes sense to use it. - ### Querys - Querys are cheap to create, low overhead, reasonably efficient to iterate. They are good for ad-hoc queries with runtime-defined conditions. An example: - - ```c - ecs_query_t *f = ecs_query(world, { - .terms = { - { ecs_id(Position) }, { ecs_id(Velocity) } - } -}); +### Caching: what is it? +Flecs is an archetype ECS, which means that entities with exactly the same components are grouped together in an "archetype" (also called a "table"). Archetypes are created on the fly whenever a new component _combination_ is created in the ECS. For example: -ecs_iter_t it = ecs_query_iter(world, f); -while (ecs_query_next(&it)) { - Position *p = ecs_field(&it, Position, 0); - Velocity *v = ecs_field(&it, Velocity, 1); - - for (int i = 0; i < it.count; i ++) { - p[i].x += v[i].x; - p[i].y += v[i].y; - } -} -``` ```cpp -flecs::query f = - world.query(); +flecs::entity e1 = world.entity(); +e1.set(Position{10, 20}); // create archetype [Position] +e1.set(Velocity{1, 2}); // create archetype [Position, Velocity] -f.each([](Position& p, Velocity& v) { - p.x += v.x; - p.y += v.y; -}); +flecs::entity e2 = world.entity(); +e2.set(Position{10, 20}); // archetype [Position] already exists +e2.set(Velocity{1, 2}); // archetype [Position, Velocity] already exists +e2.set(Mass{100}); // create archetype [Position, Velocity, Mass] + +// e1 is now in archetype [Position, Velocity] +// e2 is now in archetype [Position, Velocity, Mass] ``` -### Cached Queries -Cached queries cache the output of a filter. They are more expensive to create and have higher overhead, but are the fastest to iterate. Cached queries are the default for systems. An example: +Archetypes are important for queries. Since all entities in an archetype have the same components, and a query matches entities with specific components, a query can often match entire archetypes instead of individual entities. This is one of the main reasons why queries in an archetype ECS are fast. -```c -ecs_query_t *q = ecs_query(world, { - .terms = { - { ecs_id(Position) }, { ecs_id(Velocity) } - } -}); +The second reason that queries in an archetype ECS are fast is that they are cheap to cache. While an archetype is created for each unique component combinations, games typically only use a finite set of component combinations which are created quickly after game assets are loaded. -ecs_iter_t it = ecs_query_iter(world, q); -while (ecs_query_next(&it)) { - Position *p = ecs_field(&it, Position, 0); - Velocity *v = ecs_field(&it, Velocity, 1); +This means that instead of searching for archetypes each time a query is evaluated, a query can instead cache the list of matching archetypes. This is a cheap cache to maintain: even though entities can move in and out of archetypes, the archetypes themselves are often stable. - for (int i = 0; i < it.count; i ++) { - p[i].x += v[i].x; - p[i].y += v[i].y; - } -} -``` -```cpp -flecs::query q = - world.query(); +If none of that made sense, the main thing to remember is that a cached query does not actually have to search for entities. Iterating a cached query just means iterating a list of prematched results, and this is really, really fast. -q.each([](Position& p, Velocity& v) { - p.x += v.x; - p.y += v.y; -}); -``` +### Tradeoffs +Flecs has both cached and uncached queries. If cached queries are so fast, why even bother with uncached queries? There are four main reasons: -### Rules -Rules are a constraint-based query engine capable of traversing graphs. They are more expensive to create than filters, have low overhead, and their iteration performance depends on query complexity. An example: +- Cached queries are really fast to iterate, but take more time to create because the cache must be initialized first. -```c -ecs_query_impl_t *r = ecs_query(world, { - .terms = { - { ecs_id(Position) }, { ecs_id(Velocity) } - } -}); +- Cached queries have a higher RAM utilization, whereas uncached queries have very little overhead and are stateless. -ecs_iter_t it = ecs_query_iter(world, r); -while (ecs_query_next(&it)) { - Position *p = ecs_field(&it, Position, 0); - Velocity *v = ecs_field(&it, Velocity, 1); +- Cached queries add overhead to archetype creation/deletion, as these changes have to get propagated to caches. - for (int i = 0; i < it.count; i ++) { - p[i].x += v[i].x; - p[i].y += v[i].y; - } -} -``` -```cpp -flecs::query r = - world.query(); +- While caching archetypes is fast, some query features require matching individual entities, which are not efficient to cache (and aren't cached). -r.each([](Position& p, Velocity& v) { - p.x += v.x; - p.y += v.y; +As a rule of thumb, if you have a query that is evaluated each frame (as is typically the case with systems), they will benefit from being cached. If you need to create a query ad-hoc, an uncached query makes more sense. + +Ad-hoc queries are often necessary when a game needs to find entities that match a condition that is only known at runtime, for example to find all entities for a specific parent. + +### Cache kinds +Queries can be created with a "cache kind", which specifies the caching behavior for a query. Flecs has four different caching kinds: + +| Kind | C | C++ | Description | +|---------|---|-----|-------------| +| Default | `EcsQueryCacheDefault` | `flecs::QueryCacheDefault` | Behavior determined by query creation context | +| Auto | `EcsQueryCacheAuto` | `flecs::QueryCacheAuto` | Cache query terms that are cacheable | +| All | `EcsQueryCacheAll` | `flecs::QueryCacheAll` | Require that all query terms are cached | +| None | `EcsQueryCacheNone` | `flecs::QueryCacheNone` | No caching | + +The following sections describe each of the kinds. + +#### Default +When no cache kind is specified, queries will be created with the Default caching kind. A query with the Default kind will be created as cached (using the Auto kind, see next section) if a query is associated with an entity, and uncached if it isn't. + +What does it mean for a query to be associated with an entity? When a query is created, an application can provide an entity to associate with the query. This binds the lifecycle of the query with that of the entity, and makes it possible to lookup the query (by name) in tools like the explorer. + +The most common case of this is systems. Flecs systems have entity handles, which are associated with system queries. This means that all system queries by default are cached, unless specified otherwise. + +The rationale behind this is that if a query is associated with an entity, it will likely be reused and outlive the scope in which the query is created. Queries that are reused many times across frames are good candidates for caching. + +#### Auto +When the Auto kind is specified, all query terms that can be matched will be matched. Query features that rely on matching entire archetypes can typically be cached, whereas features that return individual entities cannot be cached. The following query features are cacheable: + +- Components, Tags and Pairs +- Terms with a `$this` source (default behavior, see variables) +- Wildcards +- Operators +- Query traversal + +There are scenarios that are cacheable in theory but aren't cached yet by the current implementation. Over time the query engine will be extended to cache terms with variables, as long as they match entire archetypes. + +Queries with the Auto kind can mix terms that are cached and uncached. + +#### All +Queries with the All kind require all terms to be cacheable. This forces a query to only use features that can be cached. If a query with kind All uses features that cannot be cached, query creation will fail. + +#### None +Queries with the None kind will not use any caching. + +### Performance tips & tricks + +#### Rematching +Queries that use traversal (either `up` or `cascade`) can trigger query "rematching". This is a process that ensures that a query cache that matches components on an entity reached through traversal stays up to date. + +A typical example of this is a query that matched a `Transform` component on a parent entity. If the `Transform` component is removed from the parent it invalidates the cache, and rematching will happen. + +Rematching can be an expensive process, especially in games with lots of archetypes. To learn if an application is slowed down by rematching, connect the explorer to it with the `flecs::monitor` module imported (see the REST API manual), and inspect the world statistics page. + +If rematching is taking up a significant amount of time, consider changing cached queries with traversal to uncached. This will increase query evaluation time, but should get rid of the query rematching cost. + +Rematching is a temporary solution to a complex problem that will eventually be solved with a much cheaper mechanism. For now however, rematching is something that needs to be monitored for queries that use query traversal features. + +#### Empty archetype optimization +Cached queries have an optimization where they store empty archetypes in a separate list from non-empty archetypes. This generally improves query iteration speed, as games can have large numbers of empty archetypes that could waste time when iterated by queries. + +However, to keep empty archetypes and non-empty archetypes in separate lists, events have to be emitted from archetypes to queries whenever their state changes. When emitting these events becomes too expensive, games can opt out of empty archetype optimization, and instead periodically cleanup empty archetypes. + +To do this, a query should be created with the `EcsQueryMatchEmptyTables` flag, and the `ecs_delete_empty_tables` function should be called periodically. An example: + +```c +// Create Position, Velocity query that matches empty archetypes. +ecs_query(world, { + .terms = { { ecs_id(Position) }, { ecs_id(Velocity) } }, + .flags = EcsQueryMatchEmptyTables }); + +// Delete empty archetypes that have been empty for 10 calls to this function. +ecs_delete_empty_tables(world, 0, 0, 10, 0, 0); ``` -For more information on how each implementation performs, see [Performance](#performance). +This will cause queries to return empty archeypes (iterators with count set to 0) which is something the application code will have to handle correctly. + +## Creating queries +This section explains how to create queries in the different language bindings and the flecs Flecs Query Language. -## Creation -This section explains how to create queries in the different language bindings and the flecs query DSL. +
+
    +
  • C -### Query Descriptors (C) -Query descriptors are the C API for creating queries. The API uses a type called `ecs_query_desc_t`, to describe the structure of a query. This type is used to create all query kinds (`ecs_query_t`, `ecs_query_cache_t`, `ecs_query_impl_t`). An example: +Query descriptors are the C API for creating queries. An example: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, { ecs_id(Velocity) }, } @@ -167,44 +178,60 @@ ecs_query_t *f = ecs_query(world, { The example shows the short notation, which looks like this when expanded: ```c -ecs_query_t *f = ecs_query_init(world, &(ecs_query_desc_t){ +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ .terms = { { ecs_id(Position) }, { ecs_id(Velocity) }, } }); ``` +Note how component types are added with the `ecs_id()` macro. This translates the component type to the component id that queries require. Tags and pairs do not require the `ecs_id()` macro: + +```c +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .terms = { + { Npc }, { ecs_pair(Likes, Bob) }, + } +}); +``` + Query descriptors can also be used by the C++ API. However because C++ does not support taking the address of a temporary, and not all language revisions support designated initializers, query descriptors in C++ should be used like this: ```c ecs_query_desc_t desc = {}; // Zero-initialize the struct desc.terms[0].id = ecs_id(Position); desc.terms[1].id = ecs_id(Velocity); -ecs_query_t *f = ecs_query_init(world, &desc); +ecs_query_t *q = ecs_query_init(world, &desc); ``` -The following table provides an overview of the query types with the init/fini functions: +Queries have to be deleted with the `ecs_query_fini` function, except when a query is associated with an entity. An example: + +```c +ecs_entity_t query_entity = ecs_new(world); +ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ + .entity = query_entity, + .terms = { + { ecs_id(Position) }, { ecs_id(Velocity) }, + } +}); -| Kind | Type | Init | Fini | Descriptor type | -|--------|----------------|-------------------|-------------------|---------------------| -| Query | `ecs_query_t` | `ecs_query_init` | `ecs_query_fini` | `ecs_query_desc_t` | -| Query | `ecs_query_cache_t` | `ecs_query_cache_init` | `ecs_query_fini` | `ecs_query_desc_t` | -| Rule | `ecs_query_impl_t` | `ecs_query_init` | `ecs_query_fini` | `ecs_query_desc_t` | +ecs_delete(world, query_entity); // Also deletes query +``` -Additionally the descriptor types for systems (`ecs_system_desc_t`) and observers (`ecs_observer_desc_t`) embed the `ecs_query_desc_t` descriptor type. +
  • +
  • C++ -### Query Builder (C++) -Query builders are the C++ API for creating queries. The builder API is built on top of the descriptor API, and adds a layer of convenience and type safety that matches modern idiomatic C++. The builder API is implemented for all query kinds (filters, cached queries, rules). An example of a simple query: +Query builders are the C++ API for creating queries. The builder API is built on top of the descriptor API, and adds a layer of convenience and type safety that matches modern idiomatic C++. An example of a simple query: ```cpp -flecs::query f = +flecs::query q = world.query(); ``` Queries created with template arguments provide a type safe way to iterate components: ```cpp -f.each([](Position& p, const Velocity& v) { +q.each([](Position& p, const Velocity& v) { p.x += v.x; p.y += v.y; }); @@ -214,34 +241,19 @@ The builder API allows for incrementally constructing queries: ```cpp flecs::query q = world.query_builder(); -f.term(); +q.with(); if (add_npc) { - f.term(); // Conditionally add + q.with(); // Conditionally add } -f.build(); // Create query +q.build(); // Create query ``` -The following table provides an overview of the query types with the factory functions: - -| Kind | Type | Factory | -|--------|-----------------|-------------------------| -| Query | `flecs::query` | `world::filter_builder` | -| Query | `flecs::query` | `world::query_builder` | -| Rule | `flecs::query` | `world::rule_builder` | - -Additional helper methods have been added to the C++ API to replace combinations of the `term` method with other methods. They are the following: - -| Term | Equivalent | -|------------------------|------------------------------| -| `.with()` | `.term()` | -| `.without()`| `.term().not_()` | -| `.read()` | `.term().read()` | -| `.write()` | `.term().write()` | +
  • +
  • Flecs Query Language -### Query DSL -The query DSL (domain specific language) is a string format that can represent a query. The query DSL is used by convenience macros in the C API like `ECS_SYSTEM` and `ECS_OBSERVER`, and makes it easier to create queries at runtime for tools like https://www.flecs.dev/explorer/. An example of a simple query in the DSL: +The Flecs Query Language is a string format that can be parsed into a query. The format is used by convenience macros in the C API like `ECS_SYSTEM` and `ECS_OBSERVER`, and makes it easier to create queries at runtime for tools like https://www.flecs.dev/explorer/. An example of a simple query in the DSL: ``` Position, [in] Velocity @@ -256,20 +268,29 @@ ECS_SYSTEM(world, Move, EcsOnUpdate, Position, [in] Velocity); Queries can be created from expressions with both the descriptor and builder APIs: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .expr = "Position, [in] Velocity" }); ``` ```cpp -flecs::query<> f = world.query_builder() +flecs::query<> q = world.query_builder() .expr("Position, [in] Velocity") .build(); ``` +For more details on the syntax, see the Flecs Query Language manual. + +
  • +
+
+ ## Iteration This section describes the different ways queries can be iterated. The code examples use filters, but also apply to cached queries and rules. -### Iterators (C) +
+
    +
  • C + In the C API an iterator object of type `ecs_iter_t` can be created for each of the query kinds, using the `ecs_query_iter`, `ecs_query_iter` and `ecs_query_iter` functions. This iterator can then be iterated with the respective `next` functions: `ecs_query_next`, `ecs_query_next` and `ecs_query_next`. An iterator can also be iterated with the `ecs_iter_next` function which is slightly slower, but does not require knowledge about the source the iterator was created for. @@ -277,7 +298,7 @@ An iterator can also be iterated with the `ecs_iter_next` function which is slig An example: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, { ecs_id(Velocity) }, } @@ -302,13 +323,15 @@ Iteration is split up into two loops: the outer loop which iterates tables, and The indices provided to the `ecs_field` function must correspond with the order in which terms have been specified in the query. This index starts counting from `1`, with index `0` reserved for the array containing entity ids. -### Each (C++) -The `each` function is the default and often fastest approach for iterating a query in C++. `each` can be called directly on a `flecs::query`, `flecs::query` and `flecs::query`. An example: +
  • +
  • C++ + +C++ has two iteration functions, `each` and `run`. The `each` function is the default and often fastest approach for iterating a query in C++. An example: ```cpp -auto f = world.query(); +auto q = world.query(); -f.each([](Position& p, const Velocity& v) { +q.each([](Position& p, const Velocity& v) { p.x += v.x; p.y += v.y; }); @@ -317,9 +340,9 @@ f.each([](Position& p, const Velocity& v) { A `flecs::entity` can be added as first argument: ```cpp -auto f = world.query(); +auto q = world.query(); -f.each([](flecs::entity e, Position& p) { +q.each([](flecs::entity e, Position& p) { std::cout << e.name() << ": " << p.x << ", " << p.y << std::endl; @@ -329,11 +352,11 @@ f.each([](flecs::entity e, Position& p) { A `flecs::iter` and `size_t` argument can be added as first arguments. This variant of `each` provides access to the `flecs::iter` object, which contains more information about the object being iterated. The `size_t` argument contains the index of the entity being iterated, which can be used to obtain entity-specific data from the `flecs::iter` object. An example: ```cpp -auto f = world.query_builder() - .term(Likes, flecs::Wildcard) +auto q = world.query_builder() + .with(Likes, flecs::Wildcard) .build(); -f.each([](flecs::iter& it, size_t index, Position& p) { +q.each([](flecs::iter& it, size_t index, Position& p) { flecs::entity e = it.entity(index); std::cout << e.name() << ": " << it.id(1).str() // prints pair @@ -346,9 +369,9 @@ When a query contains a template argument that is an empty type (a struct withou ```cpp struct Tag { }; -auto f = world.query(); +auto q = world.query(); -f.each([](flecs::entity e, Tag) { +q.each([](flecs::entity e, Tag) { std::cout << e.name() << std::endl; }); ``` @@ -358,24 +381,23 @@ Alternatively an empty type can be specified outside of the query type, which re ```cpp struct Tag { }; -auto f = world.query_builder() - .term() +auto q = world.query_builder() + .with() .build(); -f.each([](flecs::entity e) { +q.each([](flecs::entity e) { std::cout << e.name() << std::endl; }); ``` -### Run (C++) The `run` function provides an initialized iterator to a callback, and leaves iteration up to the callback implementation. Similar to C query iteration, the run callback has an outer and an inner loop. An example: ```cpp -auto f = world.query(); +auto q = world.query(); -f.run([](flecs::iter& it) { +q.run([](flecs::iter& it) { // Outer loop while (it.next()) { auto p = it.field(0); @@ -393,15 +415,14 @@ f.run([](flecs::iter& it) { }); ``` -### Iteration safety Entities can be moved between tables when components are added or removed. This can cause unwanted side effects while iterating a table, like iterating an entity twice, or missing an entity. To prevent this from happening, a table is locked by the C++ `each` and `run` functions, meaning no entities can be moved from or to it. When an application attempts to add or remove components to an entity in a table being iterated over, this can throw a runtime assert. An example: ```cpp -auto f = world.query(); +auto q = world.query(); -f.each([](flecs::entity e, Position&) { +q.each([](flecs::entity e, Position&) { e.add(); // throws locked table assert }); ``` @@ -409,10 +430,10 @@ f.each([](flecs::entity e, Position&) { This can be addressed by deferring operations while the query is being iterated: ```cpp -auto f = world.query(); +auto q = world.query(); world.defer([&]{ - f.each([](flecs::entity e, Position&) { + q.each([](flecs::entity e, Position&) { e.add(); // OK }); }); // operations are executed here @@ -421,11 +442,11 @@ world.defer([&]{ An application can also use the `defer_begin` and `defer_end` functions which achieve the same goal: ```cpp -auto f = world.query(); +auto q = world.query(); world.defer_begin(); -f.each([](flecs::entity e, Position&) { +q.each([](flecs::entity e, Position&) { e.add(); // OK }); @@ -434,12 +455,14 @@ world.defer_end(); // operations are executed here Code ran by a system is deferred by default. +
  • +
+
+ ## Reference -This section goes over the different features of queries and how they can be expressed by the query descriptor API, query builder API and in the query DSL. +This section goes over the different features of queries and how they can be expressed by the query descriptor API, query builder API and in the Flecs Query Language. ### Components -> *Supported by: filters, cached queries, rules* - A component is any single id that can be added to an entity. This includes tags and regular entities, which are ids that are not associated with a datatype. To match a query, an entity must have all the requested components. An example: @@ -460,16 +483,19 @@ flecs::entity e3 = world.entity() Only entities `e2` and `e3` match the query `Position, Velocity`. -The following sections describe how to create queries for components in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections describe how to create queries for components in the different language bindings. + +
+
    +
  • C -#### Query Descriptor (C) To query for a component in C, the `id` field of a term can be set: ```c ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { .id = ecs_id(Position) }, { .id = ecs_id(Velocity) } @@ -480,7 +506,7 @@ ecs_query_t *f = ecs_query(world, { The `id` field is guaranteed to be the first member of a term, which allows the previous code to be rewritten in this shorter form: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, { ecs_id(Velocity) } @@ -494,7 +520,7 @@ The `ecs_id` macro converts the component typename into the variable name that h ECS_TAG(world, Npc); ecs_entity_t Platoon_01 = ecs_new(world); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { Npc }, { Platoon_01 } @@ -508,7 +534,7 @@ Components can also be queried for by name by setting the `.first.name` member i ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { .first.name = "Position" }, { .first.name = "Velocity" } @@ -516,7 +542,9 @@ ecs_query_t *f = ecs_query(world, { }); ``` -#### Query Builder (C++) +
  • +
  • C++ + An easy way to query for components in C++ is to pass them as template arguments to the query factory function: ```cpp @@ -527,7 +555,7 @@ flecs::query f = This changes the returned query type, which determines the type of the function used to iterate the query: ```cpp -f.each([](Position& p, const Velocity& v) { }); +q.each([](Position& p, const Velocity& v) { }); ``` The builder API makes it possible to add components to a query without modifying the query type: @@ -535,7 +563,7 @@ The builder API makes it possible to add components to a query without modifying ```cpp flecs::query f = world.query_builder() - .term() + .with() .build(); ``` @@ -547,9 +575,9 @@ The builder API makes it possible to query for regular entity ids created at run flecs::entity Npc = world.entity(); flecs::entity Platoon_01 = world.entity(); -flecs::query<> f = world.query_builder() - .term(Npc) - .term(Platoon_01) +flecs::query<> q = world.query_builder() + .with(Npc) + .with(Platoon_01) .build(); ``` @@ -562,14 +590,16 @@ world.component(); // Create entity with name so we can look it up flecs::entity Npc = world.entity("Npc"); -flecs::query<> f = world.query_builder() - .term("Position") - .term("Npc") +flecs::query<> q = world.query_builder() + .with("Position") + .with("Npc") .build(); ``` -#### Query DSL -To query for a components in the query DSL they can be specified in a comma separated list of identifiers. The rules for resolving identifiers are the same as the `ecs_lookup` / `world.lookup` functions. An example: +
  • +
  • Flecs Query Language + +To query for a components in the Flecs Query Language they can be specified in a comma separated list of identifiers. The rules for resolving identifiers are the same as the `ecs_lookup` / `world.lookup` functions. An example: ``` Position, Velocity @@ -596,17 +626,19 @@ The entity `e` from this example will be matched by this query: Npc, Platoon_01 ``` -When an identifier in the query DSL consists purely out of numeric characters it is converted to an entity id. If in the previous example `Npc` has id `100` and `Platoon_01` has id `101`, the following query string would be equivalent: +When an identifier in the Flecs Query Language consists purely out of numeric characters it is converted to an entity id. If in the previous example `Npc` has id `100` and `Platoon_01` has id `101`, the following query string would be equivalent: ``` 100, 101 ``` -The `,` symbol in the query DSL is referred to as the `and` operator, as an entity must have all comma-separated components in order to match the query. +The `,` symbol in the Flecs Query Language is referred to as the `and` operator, as an entity must have all comma-separated components in order to match the query. -### Wildcards -> *Supported by: filters, cached queries, rules* +
  • +
+
+### Wildcards Wildcards allow a single query term to match with more than one (component) ids. Flecs supports two kinds of wildcards: | Name | DSL Symbol | C identifier | C++ identifier | Description | @@ -616,6 +648,10 @@ Wildcards allow a single query term to match with more than one (component) ids. The `Wildcard` wildcard returns an individual result for anything that it matches. The query in the following example will return twice for entity `e`, once for component `Position` and once for component `Velocity`: +
+
    +
  • C + ```c ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); @@ -624,24 +660,35 @@ ecs_entity_t e = ecs_new(world); ecs_add(world, e, Position); ecs_add(world, e, Velocity); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { EcsWildcard } } }); ``` + +
  • +
  • C++ + ```cpp flecs::entity e = world.entity() .add() .add(); -flecs::query<> f = world.query_builder() - .term(flecs::Wildcard) +flecs::query<> q = world.query_builder() + .with(flecs::Wildcard) .build(); ``` +
  • +
+
The `Any` wildcard returns a single result for the first component that it matches. The query in the following example will return once for entity `e`: +
+
    +
  • C + ```c ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity); @@ -650,43 +697,52 @@ ecs_entity_t e = ecs_new(world); ecs_add(world, e, Position); ecs_add(world, e, Velocity); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { EcsAny } } }); ``` + +
  • +
  • C++ + ```cpp flecs::entity e = world.entity() .add() .add(); -flecs::query<> f = world.query_builder() - .term(flecs::Any) +flecs::query<> q = world.query_builder() + .with(flecs::Any) .build(); ``` +
  • +
+
+ When using the `Any` wildcard it is undefined which component will be matched, as this can be influenced by other parts of the query. It is guaranteed that iterating the same query twice on the same dataset will produce the same result. Wildcards are particularly useful when used in combination with pairs (next section). ### Pairs -> *Supported by: filters, cached queries, rules* - A pair is an id that encodes two elements. Pairs, like components, can be added to entities and are the foundation for [Relationships](Relationships.md). The elements of a pair are allowed to be wildcards. When a query pair contains the `Wildcard` wildcard, a query returns a result for each matching pair on an entity. When a query pair returns an `Any` wildcard, the query returns at most a single matching pair on an entity. -The following sections describe how to create queries for pairs in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections describe how to create queries for pairs in the different language bindings. + +
+
    +
  • C -#### Query Descriptor (C) To query for a pair in C, the `id` field of a term can be set to a pair using the `ecs_pair` macro: ```c ecs_entity_t Likes = ecs_new(world); ecs_entity_t Bob = ecs_new(world); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { .id = ecs_pair(Likes, Bob) } } @@ -699,7 +755,7 @@ The `id` field is guaranteed to be the first member of a term, which allows the ecs_entity_t Likes = ecs_new(world); ecs_entity_t Bob = ecs_new(world); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_pair(Likes, Bob) } } @@ -712,7 +768,7 @@ When an element of the pair is a component type, use the `ecs_id` macro to obtai ECS_COMPONENT(world, Eats); ecs_entity_t Apples = ecs_new(world); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_pair(ecs_id(Eats), Apples) } } @@ -738,7 +794,7 @@ ecs_query_t *f_2 = ecs_query(world, { Pair queries can be created by setting their individual elements in the `first.id` and `second.id` members of a term: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { .first.id = Eats, .second.id = Apples } } @@ -768,7 +824,7 @@ ecs_query_t *f_2 = ecs_query(world, { When a query pair contains a wildcard, the `ecs_field_id` function can be used to determine the id of the pair element that matched the query: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_pair(Likes, EcsWildcard) } } @@ -787,7 +843,9 @@ while (ecs_query_next(&it)) { } ``` -#### Query Builder (C++) +
  • +
  • C++ + When both parts of a pair are types, a `flecs::pair` template can be used. Pair templates can be made part of the query type, which makes them part of the argument list of the iterator functions. An example: ```cpp @@ -800,7 +858,7 @@ flecs::query f = world.query(); // Do not use reference argument for pair -f.each([](EatsApples v) { +q.each([](EatsApples v) { // Use -> operator to access value of pair v->value ++; }); @@ -816,42 +874,42 @@ flecs::entity eats = world.component(); flecs::entity apples = world.component(); flecs::query<> f_1 = world.query_builder() - .term() + .with() .build(); flecs::query<> f_2 = world.query_builder() - .term(apples) + .with(apples) .build(); flecs::query<> f_3 = world.query_builder() - .term(eats, apples) + .with(eats, apples) .build(); ``` Individual elements of a pair can be specified with the `first` and `second` methods. The methods apply to the last added term. An example: ```cpp -flecs::query<> f = world.query_builder() - .term().first().second(apples) +flecs::query<> q = world.query_builder() + .with().first().second(apples) .build(); ``` Individual elements of a pair can be resolved by name by using the `first` and `second` methods: ```cpp -flecs::query<> f = world.query_builder() - .term().first("Eats").second("Apples") +flecs::query<> q = world.query_builder() + .with().first("Eats").second("Apples") .build(); ``` When a query pair contains a wildcard, the `flecs::iter::pair` method can be used to determine the id of the pair element that matched the query: ```cpp -flecs::query<> f = world.query_builder() - .term(flecs::Wildcard) +flecs::query<> q = world.query_builder() + .with(flecs::Wildcard) .build(); -f.each([](flecs::iter& it, size_t index) { +q.each([](flecs::iter& it, size_t index) { flecs::entity second = it.pair(0).second(); flecs::entity e = it.entity(index); @@ -861,8 +919,10 @@ f.each([](flecs::iter& it, size_t index) { }); ``` -#### Query DSL -To query for a pair in the query DSL, the elements of a pair are a comma separated list surrounded by parentheses. An example: +
  • +
  • Flecs Query Language + +To query for a pair in the Flecs Query Language, the elements of a pair are a comma separated list surrounded by parentheses. An example: ``` (Likes, Apples) @@ -889,9 +949,11 @@ A pair may contain two wildcards: (*, *) ``` -### Access modifiers -> *Supported by: filters, cached queries, rules* +
  • +
+
+### Access modifiers Access modifiers specify which components of a query can be read and/or written. The different access modifiers are: | Name | DSL identifier | C identifier | C++ identifier | Description | @@ -914,13 +976,16 @@ When a query term can either match a component from the matched entity or anothe When a query term matches a tag (a component not associated with data) with a `Default` modifier, the `None` modifier is selected. -The following sections show how to use access modifiers in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections show how to use access modifiers in the different language bindings. + +
+
    +
  • C -#### Query Descriptors (C) Access modifiers can be set using the `inout` member: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, { ecs_id(Velocity), .inout = EcsIn } @@ -928,13 +993,15 @@ ecs_query_t *f = ecs_query(world, { }); ``` -#### Query Builder (C++) +
  • +
  • C++ + Access modifiers can be set using the `inout` method: ```cpp -flecs::query<> f = world.query_builder() - .term() - .term().inout(flecs::In) +flecs::query<> q = world.query_builder() + .with() + .with().inout(flecs::In) .build(); ``` @@ -949,21 +1016,21 @@ flecs::query f = This also applies to types added with `term`: ```c -flecs::query<> f = world.query_builder() - .term() - .term() // uses flecs::In modifier +flecs::query<> q = world.query_builder() + .with() + .with() // uses flecs::In modifier .build(); ``` When a component is added by the `term` method and retrieved from a `flecs::iter` object during iteration, it must meet the constraints of the access modifiers. If the constraints are not met, a runtime assert may be thrown: ```cpp -flecs::query<> f = world.query_builder() - .term() - .term().inout(flecs::In) +flecs::query<> q = world.query_builder() + .with() + .with().inout(flecs::In) .build(); -f.run([](flecs::iter& it) { +q.run([](flecs::iter& it) { while (it.next()) { auto p = it.field(0); // OK auto p = it.field(0); // OK @@ -976,22 +1043,26 @@ f.run([](flecs::iter& it) { The builder API has `in()`, `inout()`, `out()` and `inout_none()` convenience methods: ```cpp -flecs::query<> f = world.query_builder() - .term().inout() - .term().in() +flecs::query<> q = world.query_builder() + .with().inout() + .with().in() .build(); ``` -#### Query DSL -Access modifiers in the query DSL can be specified inside of angular brackets before the component identifier: +
  • +
  • Flecs Query Language + +Access modifiers in the Flecs Query Language can be specified inside of angular brackets before the component identifier: ``` Position, [in] Velocity ``` -### Operator Overview -> *Supported by: filters, cached queries, rules* +
  • +
+
+### Operator Overview The following operators are supported by queries: | Name | DSL operator | C identifier | C++ identifier | Description | @@ -1001,18 +1072,18 @@ The following operators are supported by queries: | Not | `!` | `EcsNot` | `flecs::Not` | Must not match with term | | Optional | `?` | `EcsOptional` | `flecs::Optional` | May match with term | | Equal | `==` | `EcsPredEq` | `flecs::PredEq` | Equals entity/entity name | -| Not equal | `!=` | `EcsPredNeq` | `flecs::PredNeq` | Not equals entity/entity name | | Match | `~=` | `EcsPredMatch`| `flecs::PredMatch`| Match entity name with substring | -| AndFrom | `AND \|` | `EcsAndFrom` | `flecs::AndFrom` | Match all components from id at least once | -| OrFrom | `OR \|` | `EcsOrFrom` | `flecs::OrFrom` | Match at least one component from id at least once | -| NotFrom | `NOT \|` | `EcsNotFrom` | `flecs::NotFrom` | Don't match any components from id | +| AndFrom | `and \|` | `EcsAndFrom` | `flecs::AndFrom` | Match all components from id at least once | +| OrFrom | `or \|` | `EcsOrFrom` | `flecs::OrFrom` | Match at least one component from id at least once | +| NotFrom | `not \|` | `EcsNotFrom` | `flecs::NotFrom` | Don't match any components from id | ### And Operator -> *Supported by: filters, cached queries, rules* +The `And` operator is used when no other operators are specified. The following sections show how to use the `And` operator in the different language bindings. -The `And` operator is used when no other operators are specified. The following sections show how to use the `And` operator in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +
+
    +
  • C -#### Query Descriptor (C) When no operator is specified, `And` is assumed. The following two queries are equivalent: ```c @@ -1031,42 +1102,48 @@ ecs_query_t *f_2 = ecs_query(world, { }); ``` -#### Query Builder (C++) +
  • +
  • C++ + When no operator is specified, `And` is assumed. The following two queries are equivalent: ```cpp flecs::query f_1 = world.query(); flecs::query<> f_2 = world.query_builder() - .term() - .term() + .with() + .with() .build(); flecs::query<> f_2 = world.query_builder() - .term().oper(flecs::And) - .term().oper(flecs::And) + .with().oper(flecs::And) + .with().oper(flecs::And) .build(); ``` The builder API has a `and_` convenience method: ```cpp -flecs::query<> f = world.query_builder() - .term().and_(); // note escaping, 'and' is a C++ keyword - .term().and_(); +flecs::query<> q = world.query_builder() + .with().and_(); // note escaping, 'and' is a C++ keyword + .with().and_(); .build(); ``` -#### Query DSL +
  • +
  • Flecs Query Language + Query expressions with comma separated lists use the `And` operator: ``` Position, Velocity ``` -### Or Operator -> *Supported by: filters, cached queries, rules* +
  • +
+
+### Or Operator The `Or` operator allows for matching a single component from a list. Using the `Or` operator means that a single term can return results of multiple types. When the value of a component is used while iterating the results of an `Or` operator, an application has to make sure that it is working with the expected type. When using the `Or` operator, the terms participating in the `Or` expression are made available as a single field. Field indices obtained from an iterator need to account for this. Consider the following query: @@ -1077,14 +1154,17 @@ Position, Velocity || Speed, Mass This query has 4 terms, while an iterator for the query returns results with 3 fields. This is important to consider when retrieving the field for a term, as its index has to be adjusted. In this example, `Position` has index 1, `Velocity || Speed` has index 2, and `Mass` has index 3. -The following sections show how to use the `Or` operator in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections show how to use the `Or` operator in the different language bindings. + +
+
    +
  • C -#### Query Descriptor (C) To create a query with `Or` terms, set `oper` to `EcsOr`: ```c // Position, Velocity || Speed, Mass -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, { ecs_id(Velocity), .oper = EcsOr }, @@ -1096,7 +1176,7 @@ ecs_query_t *f = ecs_query(world, { ecs_iter_t it = ecs_query_iter(world, f); while (ecs_query_next(&it)) { Position *p = ecs_field(&it, Position, 0); - Mass *m = ecs_field(&it, Mass, 2); // not 4, because of the Or expression + Mass *m = ecs_field(&it, Mass, 2); // not 3, because of the Or expression ecs_id_t vs_id = ecs_field_id(&it, 1); if (vs_id == ecs_id(Velocity)) { @@ -1111,19 +1191,21 @@ while (ecs_query_next(&it)) { } ``` -#### Query Builder (C++) +
  • +
  • C++ + To create a query with `Or` terms, use the `oper` method with `flecs::Or`: ```cpp // Position, Velocity || Speed, Mass -flecs::query<> f = world.query_builder() - .term() - .term().oper(flecs::Or) - .term() - .term() +flecs::query<> q = world.query_builder() + .with() + .with().oper(flecs::Or) + .with() + .with() .build(); -f.run([&](flecs::iter& it) { +q.run([&](flecs::iter& it) { while (it.next()) { auto p = it.field(0); auto v = it.field(2); // not 4, because of the Or expression @@ -1145,35 +1227,43 @@ f.run([&](flecs::iter& it) { The builder API has a `or_` convenience method: ```cpp -flecs::query<> f = world.query_builder() - .term() - .term().or_(); // note escaping, 'or' is a C++ keyword - .term() - .term() +flecs::query<> q = world.query_builder() + .with() + .with().or_(); // note escaping, 'or' is a C++ keyword + .with() + .with() .build(); ``` -#### Query DSL + +
  • +
  • Flecs Query Language + To create a query with `Or` terms, use the `||` symbol: ``` Position, Velocity || Speed, Mass ``` -### Not Operator -> *Supported by: filters, cached queries, rules* +
  • +
+
+### Not Operator The `Not` operator makes it possible to exclude entities with a specified component. Fields for terms that uses the `Not` operator will never provide data. A note on performance: `Not` terms are efficient to evaluate when combined with other terms, but queries that only have `Not` terms (or [`Optional`](#optional-operator)) can be expensive. This is because the storage only maintains indices for tables that _have_ a component, not for tables that do _not have_ a component. -The following sections show how to use the `Not` operator in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections show how to use the `Not` operator in the different language bindings. + +
+
    +
  • C -#### Query Descriptor (C) To create a query with `Not` terms, set `oper` to `EcsNot`: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, { ecs_id(Velocity), .oper = EcsNot } @@ -1181,35 +1271,51 @@ ecs_query_t *f = ecs_query(world, { }); ``` -#### Query Builder (C++) +
  • +
  • C++ + To create a query with `Not` terms, use the `oper` method with `flecs::Not`: ```cpp -flecs::query<> f = world.query_builder() - .term() - .term().oper(flecs::Not) +flecs::query<> q = world.query_builder() + .with() + .with().oper(flecs::Not) .build(); ``` The builder API has a `not_` convenience method: ```cpp -flecs::query<> f = world.query_builder() - .term() - .term().not_(); // note escaping, 'not' is a C++ keyword +flecs::query<> q = world.query_builder() + .with() + .with().not_(); // note escaping, 'not' is a C++ keyword + .build(); +``` + +An application can also use the `without` method: + +```cpp +flecs::query<> q = world.query_builder() + .with() + .without(); .build(); ``` -#### Query DSL +
  • +
  • Flecs Query Language + To create a query with `Not` terms, use the `!` symbol: ``` Position, !Velocity ``` -### Optional Operator -> *Supported by: filters, cached queries, rules* +
  • +
+
+ +### Optional Operator The `Optional` operator optionally matches with a component. While this operator does not affect the entities that are matched by a query, it can provide more efficient access to a component when compared to conditionally getting the component in user code. Before accessing the value provided by an optional term, code must first check if the term was set. A note on performance: just like the `Not` operator `Optional` terms are efficient to evaluate when combined with other terms, but queries that only have `Optional` terms can be expensive. Because the `Optional` operator does not restrict query results, a query that only has `Optional` terms will match all entities. @@ -1222,13 +1328,16 @@ SpaceShip, ?(DockedTo, $planet), Planet($planet) Because the second term is optional, the variable `$planet` may or may not be set depending on whether the term was matched. As a result the third term becomes dependent: if `$planet` was not set, the term will be ignored. -The following sections show how to use the `Optional` operator in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections show how to use the `Optional` operator in the different language bindings. + +
+
    +
  • C -#### Query Descriptor (C) To create a query with `Optional` terms, set `oper` to `EcsOptional`: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, { ecs_id(Velocity), .oper = EcsOptional } @@ -1247,24 +1356,36 @@ while (ecs_query_next(&it)) { } ``` -#### Query Builder (C++) -To create a query with `Optional` terms, call the `oper` method with `flecs::Optional`: +
  • +
  • C++ + +To create a query with `Optional` terms, a component can be specified as a pointer type: + +```cpp +flecs::query<> q = world.query() + +q.each([](Position& p, Velocity* v) { + if (v) { + // ... + } +}) +``` + +Alternatively, an application can call the `oper` method with `flecs::Optional`: ```cpp -flecs::query<> f = world.query_builder() - .term() - .term().oper(flecs::Optional) +flecs::query<> q = world.query_builder() + .with() + .with().oper(flecs::Optional) .build(); -f.run([&](flecs::iter& it) { +q.run([&](flecs::iter& it) { while (it.next()) { auto p = it.field(0); if (it.is_set(1)) { auto v = it.field(1); // iterate as usual - } else if (vs_id == world.id()) { - // iterate as usual } } }); @@ -1273,22 +1394,26 @@ f.run([&](flecs::iter& it) { The builder API has an `optional` convenience method: ```cpp -flecs::query<> f = world.query_builder() - .term() - .term().optional(); +flecs::query<> q = world.query_builder() + .with() + .with().optional(); .build(); ``` -#### Query DSL +
  • +
  • Flecs Query Language + To create a query with `Optional` terms, use the `?` symbol: ``` Position, ?Velocity ``` -### Equality operators -> *Supported by: rules* +
  • +
+
+### Equality operators Equality operators (`==`, `!=`, `~=`) allow a query to ensure that a variable equals a specific entity, that the entity it stores has a specific name, or that the entity name partially matches a substring. The left hand side of an equality operator must be a variable. The right hand side of an operator can be an entity identifier or a string for the `==` and `!=` operators, and must be a string in case of the `~=` operator. For example: @@ -1311,29 +1436,34 @@ Test if variable `$this` stores an entity with a name that has substring `Fo`: $this ~= "Fo" ``` -When the equals operator (`==`) is used with a variable that has not yet been initialized, the right-hand side of the operator will be assigned to the variable. +When the equals operator (`==`) is used with a variable that has not yet been initialized, the right-hand side of the operator will be assigned to the variable. If the right hand side is a string, it will be used to lookup an entity by name. If the lookup fails, the term will not match. Other than regular operators, equality operators are set as `first`, with the left hand being `src` and the right hand being `second`. Equality operators can be combined with `And`, `Not` and `Or` terms. Terms with equality operators return no data. -#### Query Descriptor (C) +
+
    +
  • C + ```c -ecs_query_impl_t *r = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { // $this == Foo { .first.id = EcsPredEq, .second.id = Foo }, // $this != Bar { .first.id = EcsPredEq, .second.id = Bar, .oper = EcsNot }, // $this == "Foo" - { .first.id = EcsPredEq, .second = { .name = "Foo", .flags = EcsIsName }}, + { .first.id = EcsPredEq, .second = { .name = "Foo", .id = EcsIsName }}, // $this ~= "Fo" - { .first.id = EcsPredMatch, .second = { .name = "Fo", .flags = EcsIsName }}, + { .first.id = EcsPredMatch, .second = { .name = "Fo", .id = EcsIsName }}, } }); ``` -#### Query Builder (C++) +
  • +
  • C++ + ```cpp world.query_builder() // $this == Foo @@ -1347,7 +1477,9 @@ world.query_builder() .build(); ``` -#### Query DSL +
  • +
  • Flecs Query Language + ```js $this == Foo $this != Foo @@ -1355,9 +1487,11 @@ $this == "Foo" $this != "Fo" ``` -### AndFrom, OrFrom, NotFrom Operators -> *Supported by: filters, cached queries* +
  • +
+
+### AndFrom, OrFrom, NotFrom Operators The `AndFrom`, `OrFrom` and `NotFrom` operators make it possible to match a list of components that is defined outside of the query. Instead of matching the id provided in the term, the operators match the list of components _of_ the provided id as if they were provided as a list of terms with `And`, `Or` or `Not` operators. For example, if entity `e` has components `Position, Velocity` and is combined in a query with the `AndFrom` operator, entities matching the query must have both `Position` and `Velocity`. The `AndFrom`, `OrFrom` and `NotFrom` operators are especially useful when combined with prefab entities, which by default are not matched with queries themselves. Components that have the `DontInherit` property are ignored while matching the operators, which means that using a prefab in combination with `AndFrom`, `OrFrom` and `NotFrom` will not cause components like `Prefab` or `ChildOf` to be considered. @@ -1366,9 +1500,12 @@ Component lists can be organized recursively by adding an id to an entity with t Fields for terms that use the `AndFrom`, `OrFrom` or `NotFrom` operators never provide data. Access modifiers for these operators default to `InOutNone`. When a `AndFrom`, `OrFrom` or `NotFrom` operator is combined with an access modifier other than `InOutDefault` or `InOutNone` query creation will fail. -The following sections show how to use the operators in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections show how to use the operators in the different language bindings. + +
+
    +
  • C -#### Query Descriptor (C) To use the `AndFrom`, `OrFrom` and `NotFrom` operators, set `oper` to `EcsAndFrom`, `EcsOrFrom` or `EcsNotFrom` ```c @@ -1376,7 +1513,7 @@ ecs_entity_t type_list = ecs_new_w_id(world, EcsPrefab); ecs_add(world, type_list, Position); ecs_add(world, type_list, Velocity); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { type_list, .oper = EcsAndFrom }, // match Position, Velocity { type_list, .oper = EcsOrFrom }, // match Position || Velocity @@ -1385,7 +1522,9 @@ ecs_query_t *f = ecs_query(world, { }); ``` -#### Query Builder (C++) +
  • +
  • C++ + To use the `AndFrom`, `OrFrom` and `NotFrom` operators, call the `oper` method with `flecs::AndFrom`, `flecs::OrFrom` or `flecs::NotFrom`. ```cpp @@ -1393,33 +1532,37 @@ flecs::entity type_list = world.prefab() .add() .add(); -flecs::query<> f = world.query_builder() - .term(type_list).oper(flecs::AndFrom) // match Position, Velocity - .term(type_list).oper(flecs::OrFrom) // match Position || Velocity - .term(type_list).oper(flecs::NotFrom) // match !Position, !Velocity +flecs::query<> q = world.query_builder() + .with(type_list).oper(flecs::AndFrom) // match Position, Velocity + .with(type_list).oper(flecs::OrFrom) // match Position || Velocity + .with(type_list).oper(flecs::NotFrom) // match !Position, !Velocity .build(); ``` The builder API has the `and_from`, `or_from` and `not_from` convenience methods: ```cpp -flecs::query<> f = world.query_builder() - .term(type_list).and_from() - .term(type_list).or_from() - .term(type_list).not_from() +flecs::query<> q = world.query_builder() + .with(type_list).and_from() + .with(type_list).or_from() + .with(type_list).not_from() .build(); ``` -#### Query DSL +
  • +
  • Flecs Query Language + To create a query with the `AndFrom`, `OrFrom` and `NotFrom` operators in the C API, use `and`, `or` and `not` in combination with the bitwise OR operator (`|`): ``` and | type_list, or | type_list, not | type_list ``` -### Query scopes -> *Supported by: rules* +
  • +
+
+### Query scopes Query scopes are a mechanism that allows for treating the output of a number of terms as a single condition. For example, the following query has two terms with an `Or` operator that are negated by a `Not` operator: ```c @@ -1429,19 +1572,19 @@ Position, !{ Velocity || Speed } A query scope can contain any number of terms and operators. The following query has a scope with mixed operators: -``` +```c Position, !{ Mass, Velocity || Speed, !Rotation } ``` Query scopes allow for the creation of complex queries when combined with variables and relationships. The following query finds all entities that have no children with `Position`: -``` +```c Position($this), !{ ChildOf($child, $this), Position($child) } ``` Note how this is different from this query, which finds all children that don't have `Position`: -``` +```c Position($this), ChildOf($child, $this), !Position($child) ``` @@ -1449,7 +1592,7 @@ Whereas the first query only returns parents without children with `Position`, t When a scope is evaluated, the entire result set of the scope is treated as a single term. This has as side effect that any variables first declared inside the scope are not available outside of the scope. For example, in the following query the value for variable `$child` is undefined, as it is first used inside a scope: -``` +```c Position($this), !{ ChildOf($child, $this), Position($child) } ``` @@ -1459,21 +1602,26 @@ Scopes currently have the following limitations: The following examples show how to use scopes in the different language bindings: -#### Query Descriptor (C) +
+
    +
  • C + ```c -ecs_query_impl_t *r = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { // Position, !{ Velocity || Speed } { .id = ecs_id(Position) }, - { .id = EcsScopeOpen, .src.flags = EcsIsEntity, .oper = EcsNot }, + { .id = EcsScopeOpen, .src.id = EcsIsEntity, .oper = EcsNot }, { .id = ecs_id(Velocity), .oper = EcsOr }, { .id = ecs_id(Speed) }, - { .id = EcsScopeClose, .src.flags = EcsIsEntity } + { .id = EcsScopeClose, .src.id = EcsIsEntity } } }); ``` -#### Query Builder (C++) +
  • +
  • C++ + ```cpp world.query_builder() // Position, !{ Velocity || Speed } @@ -1485,30 +1633,37 @@ world.query_builder() .build(); ``` -#### Query DSL -```js +
  • +
  • Flecs Query Language + +``` Position, !{ Velocity || Speed } ``` -### Source -> *Supported by: filters, cached queries, rules* +
  • +
+
+### Source Source is a property of a term that specifies the entity on which the term should be matched. Queries support two kinds of sources: static and variable. A static source is known when the query is created (for example: match `SimTime` on entity `Game`), whereas a variable source is resolved while the query is evaluated. When no explicit source is specified, a default variable source called `$this` is used (see [Variables](#variables)). When a query only has terms with fixed sources, iterating the query will return a result at least once when it matches, and at most once if the query terms do not match wildcards. If a query has one or more terms with a fixed source that do not match the entity, the query will return no results. A source does not need to match the query when the query is created. When a term has a fixed source and the [access modifiers](#access-modifiers) are not explicitly set, the access modifier defaults to `In`, instead of `InOut`. The rationale behind this is that it encourages code to only makes local changes (changes to components owned by the matched entity) which is easier to maintain and multithread. This default can be overridden by explicitly setting access modifiers. -The following sections show how to use variable and fixed sources with the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections show how to use variable and fixed sources with the different language bindings. + +
+
    +
  • C -#### Query Descriptor (C) To specify a fixed source, set the `src.id` member to the entity to match. The following example shows how to set a source, and how to access the value provided by a term with a fixed source: ```c ecs_entity_t Game = ecs_new(world); ecs_add(world, Game, SimTime); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, // normal term, uses $this source { ecs_id(Velocity) }, // normal term, also uses $this source @@ -1539,7 +1694,7 @@ A source may also be specified by name by setting the `src.name` member: ecs_entity_t Game = ecs_entity(world, { .name = "Game" }); ecs_add(world, Game, SimTime); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(SimTime), .src.name = "Game" } } @@ -1549,7 +1704,7 @@ ecs_query_t *f = ecs_query(world, { This examples shows how to access the entities matched by the default `$this` source and a fixed source: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, // normal term, uses $this source { ecs_id(SimTime), .src.id = Game } // fixed source, match SimTime on Game @@ -1570,20 +1725,22 @@ while (ecs_query_next(&it)) { The `entities` and `count` member are solely populated by the number of entities matched by the default `$this` source. If a query only contains fixed sources, `count` will be set to 0. This is important to keep in mind, as the inner for loop from the last example would never be iterated for a query that only has fixed sources. -#### Query Builder (C++) +
  • +
  • C++ + To specify a fixed source, call the `src` method to the entity to match. The following example shows how to set a source, and how to access the value provided by a term with a fixed source: ```cpp flecs::entity Game = world.entity() .add(); -flecs::query<> f = world.query_builder() - .term() // normal term, uses $this source - .term() // normal term, also uses $this source - .term().src(Game) // fixed source, match SimTime on Game +flecs::query<> q = world.query_builder() + .with() // normal term, uses $this source + .with() // normal term, also uses $this source + .with().src(Game) // fixed source, match SimTime on Game .build(); -f.run([](flecs::iter& it) { +q.run([](flecs::iter& it) { while (it.next()) { auto p = it.field(0); auto v = it.field(1); @@ -1610,7 +1767,7 @@ flecs::query f = .build(); // Because all components are now part of the filter type, we can use each -f.each([](flecs::entity e, Position& p, Velocity& v, SimTime& st) { +q.each([](flecs::entity e, Position& p, Velocity& v, SimTime& st) { p.x += v.x * st.value; p.y += v.y * st.value; }); @@ -1626,7 +1783,7 @@ flecs::query f = .build(); // Ok (note that it.count() will be 0) -f.run([](flecs::iter& it) { +q.run([](flecs::iter& it) { while (it.next()) { auto sc = it.field(0); auto st = it.field(1); @@ -1635,17 +1792,17 @@ f.run([](flecs::iter& it) { }); // Ok -f.each([](SimConfig& sc, SimTime& st) { +q.each([](SimConfig& sc, SimTime& st) { st.value += sc.sim_speed; }); // Ok -f.each([](flecs::iter& it, size_t index, SimConfig& sc, SimTime& st) { +q.each([](flecs::iter& it, size_t index, SimConfig& sc, SimTime& st) { st.value += sc.sim_speed; }); // Not ok: there is no entity to pass to first argument -f.each([](flecs::entity e, SimConfig& sc, SimTime& st) { +q.each([](flecs::entity e, SimConfig& sc, SimTime& st) { st.value += sc.sim_speed; }); ``` @@ -1660,7 +1817,9 @@ flecs::query f = .build(); ``` -#### Query DSL +
  • +
  • Flecs Query Language + To specify a source in the DSL, use parenthesis after the component identifier. The following example uses the default `$this` source for `Position` and `Velocity`, and `Game` as source for `SimTime`. ``` @@ -1685,18 +1844,23 @@ In the previous example the source for `(Color, Diffuse)` is implicit. The follo Color($this, Diffuse), Color(Game, Sky) ``` -### Singletons -> *Supported by: filters, cached queries, rules* +
  • +
+
+### Singletons Singletons are components that are added to themselves, which can be matched by providing the component id as [source](#source). -The following sections show how to use singletons in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections show how to use singletons in the different language bindings. + +
+
    +
  • C -#### Query Descriptor (C) A singleton query is created by specifying the same id as component and source: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { Player }, { ecs_id(Position) }, @@ -1707,12 +1871,14 @@ ecs_query_t *f = ecs_query(world, { The singleton component data is accessed in the same way a component from a static [source](#source) is accessed. -#### Query Builder (C++) +
  • +
  • C++ + A singleton query can be created by specifying the same id as component and source: ```cpp flecs::query f = world.query_builder() - .term().src() // match Input on itself + .with().src() // match Input on itself .build(); ``` @@ -1720,13 +1886,15 @@ The builder API provides a `singleton` convenience function: ```cpp flecs::query f = world.query_builder() - .term().singleton() // match Input on itself + .with().singleton() // match Input on itself .build(); ``` The singleton component data is accessed in the same way a component from a static [source](#source) is accessed. -#### Query DSL +
  • +
  • Flecs Query Language + A singleton query can be created by specifying the same id as component and source: ``` @@ -1739,9 +1907,11 @@ For convenience the `$` character may be used as source, which resolves to the c Player, Position, Input($) ``` -### Relationship Traversal -> *Supported by: filters, cached queries, rules* +
  • +
+
+### Relationship Traversal Relationship traversal enables a query to search for a component by traversing a relationship. One of the most common examples of where this is useful is a Transform system, which matches `Position` on an entity and the entity's parent. To find the `Position` component on a parent entity, a query traverses the `ChildOf` relationship upwards: ![filter diagram](img/relationship_traversal.png) @@ -1781,8 +1951,8 @@ flecs::entity child = world.entity() .add(flecs::ChildOf, parent); // This filter matches 'child', because it has a parent that inherits Mass -flecs::query<> f = world.query_builder() - .term().up(flecs::ChildOf) +flecs::query<> q = world.query_builder() + .with().up(flecs::ChildOf) .build(); ``` @@ -1805,16 +1975,19 @@ This list is an overview of current relationship traversal limitations: - Traversal flags can currently only be specified for the term source. - Union relationships are not supported for traversal. -The following sections show how to use traversal in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections show how to use traversal in the different language bindings. + +
+
    +
  • C -#### Query Descriptor (C) By default queries traverse the `IsA` relationship if a component cannot be found on the matched entity. In the following example, both `base` and `inst` match the query: ```c ecs_entity_t base = ecs_new_w(world, Position); ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base); // Inherits Position -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) } } @@ -1824,9 +1997,9 @@ ecs_query_t *f = ecs_query(world, { Implicit traversal can be disabled by setting the `flags` member to `EcsSelf`. The following example only matches `base`: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { - { ecs_id(Position), .src.flags = EcsSelf } + { ecs_id(Position), .src.id = EcsSelf } } }); ``` @@ -1839,23 +2012,12 @@ ecs_entity_t child = ecs_new_w(world, Position); ecs_add_pair(world, child, EcsChildOf, parent); -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { // term matches parent & child { ecs_id(Position) }, // term just matches child, parent does not have a parent with Position - { ecs_id(Position), .src.flags = EcsUp, src.trav = EcsChildOf } - } -}); -``` - -The `EcsParent` flag can be used which is shorthand for `EcsUp` with `EcsChildOf`. The query in the following example is equivalent to the one in the previous example: - -```c -ecs_query_t *f = ecs_query(world, { - .terms = { - { ecs_id(Position) }, - { ecs_id(Position), .src.flags = EcsParent } + { ecs_id(Position), .src.id = EcsUp, .trav = EcsChildOf } } }); ``` @@ -1863,10 +2025,10 @@ ecs_query_t *f = ecs_query(world, { If a query needs to match a component from both child and parent, but must also include the root of the tree, the term that traverses the relationship can be made optional. The following example matches both `parent` and `child`. The second term is not set for the result that contains `parent`. ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, - { ecs_id(Position), .src.flags = EcsParent, .oper = EcsOptional } + { ecs_id(Position), .src.id = EcsUp, .oper = EcsOptional } } }); ``` @@ -1877,7 +2039,7 @@ By adding the `EcsCascade` flag, a query will iterate the hierarchy top-down. Th ecs_query_t *q = ecs_query(world, { .terms = { { ecs_id(Position) }, - { ecs_id(Position), .src.flags = EcsCascade|EcsParent, .oper = EcsOptional } + { ecs_id(Position), .src.id = EcsCascade|EcsUp, .oper = EcsOptional } } }); ``` @@ -1885,9 +2047,9 @@ ecs_query_t *q = ecs_query(world, { Relationship traversal can be combined with fixed [source](#source) terms. The following query matches if the `my_widget` entity has a parent with the `Window` component: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { - { ecs_id(Window), .src.flags = EcsParent, .src.id = my_widget } + { ecs_id(Window), .src.id = my_widget|EcsUp } } }); ``` @@ -1903,14 +2065,15 @@ ecs_query_t *f_1 = ecs_query(world, { ecs_query_t *f_2 = ecs_query(world, { .terms = {{ - .id = ecs_id(Position), // match Position - .src.flags = EcsSelf | EcsUp // first match self, traverse upwards while not found - .trav = EcsIsA, // traverse using the IsA relationship + .id = ecs_id(Position) | EcsSelf | EcsUp, // first match self, traverse upwards while not found + .trav = EcsIsA, // traverse using the IsA relationship }} }); ``` -#### Query Builder (C++) +
  • +
  • C++ + By default queries traverse the `IsA` relationship if a component cannot be found on the matched entity. In the following example, both `base` and `inst` match the query: ```cpp @@ -1926,8 +2089,8 @@ flecs::query<> f = world.query(); Implicit traversal can be disabled by calling the `self` method for the term. The following example only matches `base`: ```cpp -flecs::query<> f = world.query_builder() - .term().self() +flecs::query<> q = world.query_builder() + .with().self() .build(); ``` @@ -1940,29 +2103,29 @@ flecs::entity parent = world.entity() flecs::entity child = world.entity() .child_of(parent); // short for .add(flecs::ChildOf, parent) -flecs::query<> f = world.query_builder() +flecs::query<> q = world.query_builder() // term matches parent & child - .term() + .with() // term just matches child, parent does not have a parent with Position - .term().up(flecs::ChildOf) + .with().up(flecs::ChildOf) .build(); ``` The `parent` method can be used which is shorthand for `up(flecs::ChildOf)`. The query in the following example is equivalent to the one in the previous example: ```cpp -flecs::query<> f = world.query_builder() - .term() - .term().parent() +flecs::query<> q = world.query_builder() + .with() + .with().parent() .build(); ``` If a query needs to match a component from both child and parent, but must also include the root of the tree, the term that traverses the relationship can be made optional. The following example matches both `parent` and `child`. The second term is not set for the result that contains `parent`. ```cpp -flecs::query<> f = world.query_builder() - .term() - .term().parent().optional() +flecs::query<> q = world.query_builder() + .with() + .with().parent().optional() .build(); ``` @@ -1970,16 +2133,16 @@ By calling the `cascade` method, a query will iterate the hierarchy top-down. No ```cpp flecs::query<> q = world.query_builder() - .term() - .term().cascade(flecs::ChildOf).optional() + .with() + .with().cascade(flecs::ChildOf).optional() .build(); ``` Relationship traversal can be combined with fixed [source](#source) terms. The following query matches if the `my_widget` entity has a parent with the `Window` component: ```cpp -flecs::query<> f = world.query_builder() - .term().src(my_widget).parent() +flecs::query<> q = world.query_builder() + .with().src(my_widget).parent() .build(); ``` @@ -1988,14 +2151,16 @@ The two queries in the following example are equivalent, and show how the implic ```cpp flecs::query<> f = world.query(); -flecs::query<> f = world.query_builder() - .term() // match Position +flecs::query<> q = world.query_builder() + .with() // match Position .self() // first match self .up(flecs::IsA) // traverse IsA upwards while not found .build(); ``` -#### Query DSL +
  • +
  • Flecs Query Language + By default queries traverse the `IsA` relationship if a component cannot be found on the matched entity. The following query matches `Position` on entities that either have the component or inherit it: ``` @@ -2045,9 +2210,11 @@ Position Position(self|up(IsA)) ``` -### Instancing -> *Supported by: filters, cached queries, rules* +
  • +
+
+### Instancing > **Note**: this section is useful when optimizing code, but is not required knowledge for using queries. Instancing is the ability to return results with fields that have different numbers of elements. An example of where this is relevant is a query with terms that have both variable and fixed [sources](#source): @@ -2088,7 +2255,7 @@ flecs::query f = world.query(); The filter in this example will match both `inst_1`, `inst_2` because they inherit `Mass`, and `ent_1` and `ent_2` because they own `Mass`. The following example shows an example of code that iterates the filter: ```cpp -f.run([](flecs::iter& it) { +q.run([](flecs::iter& it) { while (it.next()) { auto p = it.field(0); auto m = it.field(1); @@ -2159,15 +2326,18 @@ Note that when iteration is not instanced, `inst_1` and `inst_2` are returned in However, what the diagram also shows is that code for instanced iterators must handle results where `Mass` is either an array or a single value. This is the tradeoff of instancing: faster iteration at the cost of more complex iteration code. -The following sections show how to use instancing in the different language bindings. The code examples use filter queries, but also apply to queries and rules. +The following sections show how to use instancing in the different language bindings. + +
+
    +
  • C -#### Query Descriptor (C) Queries can be instanced by setting the `instanced` member to true: ```c -ecs_query_t *f = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { - { ecs_id(Position), src.flags = EcsSelf }, // Never inherit Position + { ecs_id(Position), src.id = EcsSelf }, // Never inherit Position { ecs_id(Mass) } }, @@ -2200,7 +2370,9 @@ while (ecs_query_next(&it)) { Note how the `ecs_field_is_self` test is moved outside of the for loops. This keeps conditional statements outside of the core loop, which enables optimizations like auto-vectorization. -#### Query Builder (C++) +
  • +
  • C++ + Queries can be instanced by calling the `instanced` method: ```cpp @@ -2212,7 +2384,7 @@ flecs::query f = world.query_builder() .instanced() .build(); -f.run([](flecs::iter& it) { +q.run([](flecs::iter& it) { while (it.next()) { auto p = it.field(0); auto m = it.field(1); @@ -2301,26 +2473,52 @@ An application can set the `$this` variable or `$Location` variables, or both, b The following sections show how to use queries in the different language bindings. The code examples use rules queries, which currently are the only queries that support using variables other than `$this`. -#### Query Descriptor (C) +
    +
      +
    • C + Query variables can be specified by setting the `name` member in combination with setting the `EcsIsVariable` bit in the `flags` member: ```c // SpaceShip, (DockedTo, $Location), Planet($Location) -ecs_query_impl_t *r = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { { .id = SpaceShip }, { .first.id = DockedTo, .second = { .name = "Location", - .flags = EcsIsVariable + .id = EcsIsVariable } }, { .id = Planet, .src = { .name = "Location", - .flags = EcsIsVariable + .id = EcsIsVariable + } + } + } +}); +``` + +Alternatively an application can specify a name with the `$` prefix to indicate it is a variable: + +```c +// SpaceShip, (DockedTo, $Location), Planet($Location) +ecs_query_t *q = ecs_query(world, { + .terms = { + { .id = SpaceShip }, + { + .first.id = DockedTo, + .second = { + .name = "$Location" + } + }, + { + .id = Planet, + .src = { + .name = "$Location" } } } @@ -2342,14 +2540,16 @@ ecs_iter_set_var(&it, location_var, earth); // Iterate as usual ``` -#### Query Builder (C++) +
    • +
    • C++ + Query variables can be specified by specifying a name with a `$` prefix: ```cpp auto r = world.query_builder() - .term() - .term().second("$Location") - .term().src("$Location") + .with() + .with().second("$Location") + .with().src("$Location") .build(); ``` @@ -2357,9 +2557,9 @@ Alternatively, variables can also be specified using the `var` method: ```cpp auto r = world.query_builder() - .term() - .term().second().var("Location") - .term().src().var("Location") + .with() + .with().second().var("Location") + .with().src().var("Location") .build(); ``` @@ -2385,8 +2585,6 @@ r.iter().set_var("Location", earth).each([]{ ``` ### Change Detection -> *Supported by: cached queries* - Change detection makes it possible for applications to know whether data matching a query has changed. Changes are tracked at the table level, for each component in the table. While this is less granular than per entity tracking, the mechanism has minimal overhead, and can be used to skip entities in bulk. Change detection works by storing a list of counters on tracked tables, where each counter tracks changes for a component in the table. When a component in the table changes, the corresponding counter is increased. An additional counter is stored for changes that add or remove entities to the table. Queries with change detection store a copy of the list of counters for each table in the cache, and compare counters to detect changes. To reduce overhead, counters are only tracked for tables matched with queries that use change detection. @@ -2415,7 +2613,10 @@ When a query iterates a table for which changes are tracked and the query has `i The following sections show how to use change detection in the different language bindings. The code examples use cached queries, which is the only kind of query for which change detection is supported. -#### Query Descriptor (C) +
      +
        +
      • C + The following example shows how the change detection API is used in C: ```c @@ -2463,7 +2664,9 @@ while (ecs_query_next(&it)) { } ``` -#### Query Builder (C++) +
      • +
      • C++ + The following example shows how the change detection API is used in C++: ```cpp @@ -2508,8 +2711,6 @@ q_read.run([](flecs::iter& it) { ``` ### Sorting -> *Supported by: cached queries* - Sorted queries allow an application to specify a component that entities should be sorted on. Sorting is enabled by setting the `order_by` function in combination with the component to order on. Sorted queries sort the tables they match with when necessary. To determine whether a table needs to be sorted, sorted queries use [change detection](#change-detection). A query determines whether a sort operation is needed when an iterator is created for it. > Because sorting relies on change detection, it has the same limitations with respect to detecting changes. When using sorted queries, make sure a query is able to detect the changes necessary for knowing when to (re)sort. @@ -2549,7 +2750,10 @@ To minimize time spent on sorting, the results of a sort are cached. The perform The following sections show how to use sorting in the different language bindings. The code examples use cached queries, which is the only kind of query for which change detection is supported. -#### Query Descriptor (C) +
        +
          +
        • C + The following example shows how to use sorted queries in C: ```c @@ -2592,7 +2796,9 @@ int compare_entity(ecs_entity_t e1, const void *v1, ecs_entity_t e2, const void } ``` -#### Query Builder (C++) +
        • +
        • C++ + The following example shows how to use sorted queries in C++: ```cpp @@ -2610,7 +2816,7 @@ Queries may specify a component id if the component is not known at compile time flecs::entity depth_id = world.component(); auto q = world.query_builder() - .term(depth_id).in() + .with(depth_id).in() .order_by(depth_id, [](flecs::entity_t e1, const void *d1, flecs::entity_t e2, const void *d2) { // Generic sort code ... }) @@ -2628,8 +2834,6 @@ auto q = world.query_builder() ``` ### Grouping -> *Supported by: cached queries* - Grouping is the ability of queries to assign an id ("group id") to a set of tables. Grouped tables are iterated together, as they are stored together in the query cache. Additionally, groups in the query cache are sorted by group id, which guarantees that tables with a lower group id are iterated after tables with a higher group id. Grouping is only supported for cached queries. Group ids are local to a query, and as a result queries with grouping do not modify the tables they match with. @@ -2651,7 +2855,10 @@ Grouped iterators, when used in combination with a good group_by function are on The following sections show how to use sorting in the different language bindings. The code examples use cached queries, which is the only kind of query for which change detection is supported. -#### Query Descriptor (C) +
          +
            +
          • C + The following example shows how grouping can be used to group entities that are in the same game region. ```c @@ -2692,7 +2899,9 @@ uint64_t group_by_target( } ``` -#### Query Builder (C++) +
          • +
          • C++ + The following example shows how grouping can be used to group entities that are in the same game region. ```cpp @@ -2713,7 +2922,7 @@ flecs::entity unit_02 = world.entity() // Create query that groups entities that are in the same region flecs::query<> q = world.query_builder() - .term() + .with() .group_by([]( flecs::world_t *world, flecs::table_t *table, @@ -2734,8 +2943,6 @@ flecs::query<> q = world.query_builder() ``` ### Component Inheritance -> *Supported by: rules* - Component inheritance allows for a query to match entities with a component and all subsets of that component, as defined by the `IsA` relationship. Component inheritance is enabled for all queries by default, for components where it applies. It is possible to prevent inheriting from a component from by adding the [Final](Relationships.md#final-property) property. Queries for components with the `Final` property will not attempt to resolve component inheritance. @@ -2744,7 +2951,10 @@ Inheritance relationships can, but are not required to mirror inheritance of the The following sections show how to use component inheritance in the different language bindings. The code examples use rules, which is the only kind of query for which component inheritance is currently supported. -#### Query Descriptor (C) +
            +
              +
            • C + The following example shows a rule that uses component inheritance to match entities: ```c @@ -2756,14 +2966,16 @@ ecs_entity_t unit_01 = ecs_new_w_id(world, MeleeUnit); ecs_entity_t unit_02 = ecs_new_w_id(world, RangedUnit); // Matches entities with Unit, MeleeUnit and RangedUnit -ecs_query_impl_t *r = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = {{ Unit }} }); // Iterate as usual ``` -#### Query Builder (C++) +
            • +
            • C++ + The following example shows a rule that uses component inheritance to match entities: ```cpp @@ -2781,8 +2993,6 @@ flecs::query r = world.query(); ``` ### Transitive Relationships -> *Supported by: rules* - When a [transitive relationship](Relationships.md#transitive-relationships) is used by a query, a query will automatically look up or test against pairs that satisfy the transitive property. Transitivity is usually defined as: > If R(X, Y) and R(Y, Z) then R(X, Z) @@ -2795,7 +3005,10 @@ An example of a builtin relationship that has the `Transitive` property is the ` The following sections show how to use transitive relationships in the different language bindings. The code examples use rules, which is the only kind of query for which transitive relationships are currently supported. -#### Query Descriptor (C) +
              +
                +
              • C + The following example shows a rule that uses transitivity to match entities that are located in New York: ```c @@ -2808,7 +3021,7 @@ ecs_entity_t CentralPark = ecs_new_w_pair(world, LocatedIn, Manhattan); ecs_entity_t Bob = ecs_new_w_pair(world, LocatedIn, CentralPark); // Matches ManHattan, CentralPark, Bob -ecs_query_impl_t *r = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = {{ ecs_pair(LocatedIn, NewYork) }} }); @@ -2822,8 +3035,8 @@ Queries for transitive relationships can be compared with variables. This query // - ManHattan (Place = NewYork) // - CentralPark (Place = ManHattan, NewYork) // - Bob (Place = CentralPark, ManHattan, NewYork) -ecs_query_impl_t *r = ecs_query(world, { - .terms = {{ .first.id = LocatedIn, .second.name = "Place", .second.flags = EcsIsVariable }} +ecs_query_t *q = ecs_query(world, { + .terms = {{ .first.id = LocatedIn, .second.name = "$Place" }} }); ``` @@ -2838,15 +3051,17 @@ ecs_add_id(world, NewYork, City); // - ManHattan (Place = NewYork) // - CentralPark (Place = NewYork) // - Bob (Place = NewYork) -ecs_query_impl_t *r = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = { - { .first.id = LocatedIn, .second.name = "Place", .second.flags = EcsIsVariable }, - { .id = City, .src.name = "Place", .src.flags = EcsIsVariable } + { .first.id = LocatedIn, .second.name = "$Place" }, + { .id = City, .src.name = "$Place" } } }); ``` -#### Query Builder (C++) +
              • +
              • C++ + The following example shows a rule that uses transitivity to match entities that are located in New York: ```cpp @@ -2862,7 +3077,7 @@ flecs::entity Bob = world.entity().add(CentralPark); // Matches ManHattan, CentralPark, Bob flecs::query<> r = world.query_builder() - .term(NewYork) + .with(NewYork) .build(); // Iterate as usual @@ -2876,7 +3091,7 @@ Queries for transitive relationships can be compared with variables. This query // - CentralPark (Place = ManHattan, NewYork) // - Bob (Place = CentralPark, ManHattan, NewYork) flecs::query<> r = world.query_builder() - .term().second("$Place") + .with().second("$Place") .build(); ``` @@ -2893,12 +3108,14 @@ NewYork.add(City); // - CentralPark (Place = NewYork) // - Bob (Place = NewYork) flecs::query<> r = world.query_builder() - .term().second("$Place") - .term().src("$Place") + .with().second("$Place") + .with().src("$Place") .build(); ``` -#### Query DSL +
              • +
              • Flecs Query Language + Transitivity in a query is enabled by adding the `Transitive` property to a relationship. As a result, a query for a transitive relationship looks the same as a query for a non-transitive relationship. The following examples show the queries used in the C/C++ examples: Match all entities located in New York: @@ -2919,9 +3136,11 @@ Return the city entities are in: (LocatedIn, $Place), City($Place) ``` -### Reflexive Relationships -> *Supported by: rules* +
              • +
              +
              +### Reflexive Relationships When a query matches a [reflexive](Relationships.md#reflexive-property) relationship, a query term will evaluate to true if the source and target are equal. Reflexivity can be defined as: > R(X, X) @@ -2930,7 +3149,10 @@ For example, if relationship IsA (R) is reflexive, then a Tree (X) is a Tree (X) The following sections show how to use transitive relationships in the different language bindings. The code examples use rules, which is the only kind of query for which transitive relationships are currently supported. -#### Query Descriptor (C) +
              +
                +
              • C + The following example shows a rule that uses the `IsA` reflexive relationship: ```c @@ -2938,12 +3160,14 @@ ecs_entity_t Tree = ecs_new(world); ecs_entity_t Oak = ecs_new_w_pair(world, EcsIsA, Tree); // Matches Tree, Oak -ecs_query_impl_t *r = ecs_query(world, { +ecs_query_t *q = ecs_query(world, { .terms = {{ ecs_pair(EcsIsA, Tree )}} }); ``` -#### Query Builder (C++) +
              • +
              • C++ + The following example shows a rule that uses the `IsA` reflexive relationship: ```cpp @@ -2952,141 +3176,19 @@ flecs::entity Oak = world.entity().is_a(Tree); // Matches Tree, Oak flecs::query<> r = world.query_builder() - .term(flecs::IsA, Tree) + .with(flecs::IsA, Tree) .build(); ``` -#### Query DSL +
              • +
              • Flecs Query Language + Reflexivity in a query is enabled by adding the `Reflexive` property to a relationship. As a result, a query for a reflexive relationship looks the same as a query for a non-reflexive relationship. The following example shows the query used in the C/C++ examples: ``` (IsA, Tree) ``` -## Performance -This section describes performance characteristics for each query type. - -### Query -A filter is a data type that describes the structure of a query in Flecs. Querys are the cheapest to create, as creating one just requires initializing the value of the filter object. - -Querys can serve different purposes, like iterating all matching entities, or testing if a specific entity matches with a filter. Querys can also be used to filter the output of other queries, including other filters. - -While the exact way a filter is evaluated depends on what kind of filter it is, almost all filter evaluation follows steps similar to the ones in this diagram: - -![filter diagram](img/filter_diagram.png) - -Each node in the diagram represents a function that can return either true or false. When a node returns true, evaluation moves on to the next node. When it returns false, evaluation goes back one node. These kinds of functions are called [predicates](https://en.wikipedia.org/wiki/Predicate_(mathematical_logic)), and this evaluation process is called [backtracking](https://en.wikipedia.org/wiki/Backtracking). - -Whether a node returns true or false depends on what its function does: - -- The first node invokes the function `select(Position)`. This function finds all *tables* with `Position`, and will forward these tables to the next node. As long as `select` is able to find more tables with `Position`, the node will keep returning true. When no more tables can be found the node returns false. - -- The second node invokes the function `with(Velocity)`. This function takes the table found by the previous node and returns true if the table has `Velocity`, and false if it does not. - -Both functions are O(1) operations. - -When a table reaches the `yield` node it means that it matched all parts of the filter, and it will be returned by the iterator doing the evaluation. - -> A table groups all entities that have _exactly_ the same components. Thus if one entity in a table matches a node, all entities in the table match the node. This is one of the main reasons queries are fast: instead of checking each individual entity for components, we can eliminate a table with thousands of entities in a single operation. - -Because filters are fast to create, have low overhead, and are reasonably efficient to iterate, they are the goto solution for when an application cannot know in advance what it needs to query for, like finding all children for a specific entity: - -```c -ecs_query_f fs = ECS_FILTER_INIT; -ecs_query_t *f = ecs_query(world, { - .storage = &fs, // optional, but prevents allocation - .terms = {{ ecs_childof(e) }} -}); - -ecs_iter_t child_it = ecs_query_iter(f); -while (ecs_query_next(&child_it)) { - for (int c = 0; c < child_it.count; c ++) { - printf("child %s\n", ecs_get_name(world, - child_it.entities[c])); - } -} - -ecs_query_fini(f); -``` -```cpp -auto f = world.query_builder() - .term(flecs::ChildOf, e) - .build(); - -f.each([](flecs::entity child) { - std::cout << child.path().str() << "\n"; -}); -``` - -While filter evaluation for a single component is very fast, each additional component adds some overhead. This is not just because of the time it takes to do an additional check, but because more tables will get discarded during evaluation. The cost of discarded tables adds up as they pass through one or more nodes before getting rejected, consuming resources while not contributing to the query result. - -This cost can be particularly significant for filters that match a large (5-10) number of components in applications with many (>1000s) tables. While Flecs implements several strategies to limit this overhead, like storing empty tables separately from non-empty tables, this is something to keep in mind when using filters. - -### Cached Queries -A cached query is an object that caches the output of a filter. Cached queries are significantly faster to iterate than a filter, as an iterator just needs to walk the list of cache entries and return each one to the application. - -This also makes the performance of cached queries more predictable. The overhead per returned table for a filter depends on how many components it matches with, and how many tables it had to discard. For a cached query the cost per table is much lower, and is constant time. - -> [A benchmark showing the difference between cached queries and filters](https://twitter.com/ajmmertens/status/1509473999205507080). - -This predictable behavior is why cached queries are the default for Flecs systems. Once tables are created and caches are initialized, the (often time critical) main loop of an application does not have to spend time matching entities with systems, which frees up resources for application logic. - -Another benefit of cached queries is that they are compatible with good code practices. Cached queries add minimal overhead to systems, which means applications do not have to compromise on design where the cost of evaluating a query has to be weighed against the benefit of having many small decoupled systems. - -The following example shows how cached queries are used: - -```c -// Creates query, populates the cache with existing tables -ecs_query_t *q = ecs_query(world, { - .terms = { - { ecs_id(Position) }, { ecs_id(Velocity) } - } -}); - -// When a new table is created that matches the query, it is -// added to the cache -ecs_entity_t e = ecs_new(world); -ecs_add(world, e, Position); -ecs_add(world, e, Velocity); - -// Iterate the tables in the cache -ecs_iter_t it = ecs_query_iter(world, q); -while (ecs_query_next(&it)) { - Position *p = ecs_field(&it, Position, 0); - Velocity *v = ecs_field(&it, Velocity, 1); - - for (int i = 0; i < it.count; i ++) { - p[i].x += v[i].x; - p[i].y += v[i].y; - } -} -``` -```cpp -// Creates query, populates the cache with existing tables -auto q = world.query(); - -// When a new table is created that matches the query, it is -// added to the cache -auto e = world.entity() - .add() - .add(); - -// Iterate the tables in the cache -q.each([](Position& p, Velocity& v) { - p.x += v.x; - p.y += v.y; -}); -``` - -The scenarios described so far are best case scenarios for cached queries, where their performance is amongst the fastest of any ECS implementation. To build games that perform well however, it also helps to know when cached queries perform badly: - -- Cached queries do not perform well when they are repeatedly created and destroyed in loops, or when they are only used a handful of times. The overhead of initializing the query cache and the cost of keeping it up to date could in those cases be higher than using a filter. - -- The cost of table creation/deletion increases with the number of queries in the world. Queries use observers to get notified of new tables. That means that for each table event, the number of queries notified is the number of queries with at least one component in common with the table. - -- ECS operations can cause matched tables to no longer match. A simple example is that of a query matching `Position` on a parent entity, where the component is removed from the parent. This triggers cache revalidation, where a query reevaluates its filter to correct invalid entries. When this happens for a large number of queries and tables, this can be time consuming. - -- Performance can degrade when a query needs to traverse a deep (>50 levels) hierarchy in order to determine if a table matches. If a hierarchy has deeply nested children and a parent component can only be found on the root, a query will have to traverse this tree likely for multiple tables, which is expensive. - - - +
              • +
              +
              diff --git a/flecs.h b/flecs.h index c5bd9921e..cbd8ea50b 100644 --- a/flecs.h +++ b/flecs.h @@ -3258,7 +3258,7 @@ typedef enum ecs_oper_kind_t { typedef enum ecs_query_cache_kind_t { EcsQueryCacheDefault, /**< Behavior determined by query creation context */ EcsQueryCacheAuto, /**< Cache query terms that are cacheable */ - EcsQueryCacheAll, /**< Require that all query terms can be cached */ + EcsQueryCacheAll, /**< Require that all query terms are cached */ EcsQueryCacheNone, /**< No caching */ } ecs_query_cache_kind_t;