doc: updated entity.md

This commit is contained in:
Michele Caini
2020-01-06 00:40:45 +01:00
parent d202bd3e50
commit b6dae2fe57
2 changed files with 166 additions and 274 deletions

2
TODO
View File

@@ -13,7 +13,9 @@
* static reflection, hint: template<> meta_type_t<Type>: meta_descriptor<name, func..., props..., etc...>
* add meta support to registry (eg entity for each component and opaque get)
* allow for custom stamp functions
* merge create() and create(hint), is it a viable solution?
* observer: user defined filters (eg .replace<T, &function> or .group<T, U, &func>)
* use underlying_type as entity type within pools and registry? it would make different registries work together flawlessy
* can we write a bool conv func for entt::entity that silently compares it to null?
* any-of rule for views/groups (eg entity has A and any of B/C/D)
- get -> all, exclude -> none

View File

@@ -28,7 +28,6 @@
* [Continuous loader](#continuous-loader)
* [Archives](#archives)
* [One example to rule them all](#one-example-to-rule-them-all)
* [Runtime components](#runtime-components)
* [Views and Groups](#views-and-groups)
* [Views](#views)
* [Runtime views](#runtime-views)
@@ -61,8 +60,7 @@ used mostly in game development.
## A bitset-free entity-component system
`EnTT` offers a _bitset-free_ entity-component system that doesn't require users
to specify the set of components neither at compile-time nor at runtime before
being able to use the library itself.<br/>
to specify the set of components neither at compile-time nor at runtime.<br/>
This is why users can instantiate the core class simply like:
```cpp
@@ -75,9 +73,8 @@ In place of its more annoying and error-prone counterpart:
entt::registry<comp_0, comp_1, ..., comp_n> registry;
```
Furthermore, there is no need to indicate to the library in any way that a type
of component exists and will be used sooner or later. When the time comes, users
can just use it and that's all.
Furthermore, it isn't necessary to announce the existence of a component type.
When the time comes, just use it and that's all.
## Pay per use
@@ -94,10 +91,7 @@ perfomance sometimes and I've always wondered why this kind of tools do not
leave me the choice.<br/>
`EnTT` follows a completely different approach. It gets the best out from the
basic data structures and gives users the possibility to pay more for higher
performance where needed.<br/>
The disadvantage of this approach is that users need to know the systems they
are working on and the tools they are using. Otherwise, the risk to ruin the
performance along critical paths is high.
performance where needed.
So far, this choice has proven to be a good one and I really hope it can be for
many others besides me.
@@ -108,13 +102,11 @@ many others besides me.
directly access all the instances of a given component type `T`.<br/>
This was a guideline and a design decision that influenced many choices, for
better and for worse. I cannot say whether it will be useful or not to the
reader, but it's worth to mention it, because it's one of the corner stones of
reader, but it's worth to mention it since it's one of the corner stones of
this library.
Many of the tools described below, from the registry to the views and up to the
groups give the possibility to get this information and have been designed
around this need, which was and remains one of my main requirements during the
development.<br/>
Many of the tools described below give the possibility to get this information
and have been designed around this need.<br/>
The rest is experimentation and the desire to invent something new, hoping to
have succeeded.
@@ -126,37 +118,25 @@ all systems can be free functions and there is no need to define them as classes
This is possible because the main class with which the users will work provides
all what is needed to act as the sole _source of truth_ of an application.
To be honest, this point became part of the design principles at a later date,
but has also become one of the cornerstones of the library to date, as stateless
systems are widely used and appreciated in general.
# Vademecum
The registry to store, the views and the groups to iterate. That's all.
An entity (the _E_ of an _ECS_) is an opaque identifier that users should just
use as-is and store around if needed. Do not try to inspect an entity
identifier, its format can change in future and a registry offers all the
functionalities to query them out-of-the-box. The underlying type of an entity
(either `std::uint16_t`, `std::uint32_t` or `std::uint64_t`) can be specified
when defining a registry. In fact, an `entt::registry` is nothing more than an
alias for `entt::basic_registry<entt::entity>` and `entt::entity` is a distinct
type that implements the concept of _entity identifier_.<br/>
Components (the _C_ of an _ECS_) should be plain old data structures or more
complex and movable data structures with a proper constructor. Actually, the
sole requirement of a component type is that it must be both move constructible
and move assignable. They are list initialized by using the parameters provided
to construct the component itself. No need to register components or their types
An entity (the _E_ of an _ECS_) is an opaque identifier that users should use
as-is. Inspecting an identifier isn't recommended since its format can change in
future and a registry has all the functionalities to query them out-of-the-box.
The type `entt::entity` implements the concept of _entity identifier_.<br/>
Components (the _C_ of an _ECS_) must be both move constructible and move
assignable. They are list initialized by using the parameters provided to
construct the component itself. No need to register components or their types
neither with the registry nor with the entity-component system at all.<br/>
Systems (the _S_ of an _ECS_) are just plain functions, functors, lambdas or
whatever users want. They can accept a registry, a view or a group of any type
and use them the way they prefer. No need to register systems or their types
neither with the registry nor with the entity-component system at all.
Systems (the _S_ of an _ECS_) can be plain functions, functors, lambdas and so
on. It's not required to announce them in any case and have no requirements.
The following sections will explain in short how to use the entity-component
system, the core part of the whole library.<br/>
In fact, the project is composed of many other classes in addition to those
describe below. For more details, please refer to the inline documentation.
The following sections explain in short how to use the entity-component system,
the core part of the whole library.<br/>
The project is composed of many other classes in addition to those describe
below. For more details, please refer to the inline documentation.
# The Registry, the Entity and the Component
@@ -164,11 +144,10 @@ A registry can store and manage entities, as well as create views and groups to
iterate the underlying data structures.<br/>
The class template `basic_registry` lets users decide what's the preferred type
to represent an entity. Because `std::uint32_t` is large enough for almost all
the cases, there exists also the type `entt::entity` for it, as well as the
alias `entt::registry` for `entt::basic_registry<entt::entity>`.
the cases, there exists also the type `entt::entity` for it and the alias
`entt::registry` for `entt::basic_registry<entt::entity>`.
Entities are represented by _entity identifiers_. An entity identifier is an
opaque type that users should not inspect or modify in any way. It carries
Entities are represented by _entity identifiers_. An entity identifier carries
information about the entity itself and its version.<br/>
User defined identifiers can be introduced by means of the `ENTT_OPAQUE_TYPE`
macro if needed.
@@ -183,10 +162,9 @@ auto entity = registry.create();
registry.destroy(entity);
```
The `create` member function has also an overload that accepts two iterators and
can be used to generate multiple entities at once efficiently. Similarly, the
`destroy` member function works also with a range of entities and destroys them
all:
The `create` member function accepts also a hint and has an overload that gets
two iterators and can be used to generate multiple entities at once efficiently.
Similarly, the `destroy` member function works also with a range of entities:
```cpp
// destroys all the entities in a range
@@ -194,17 +172,14 @@ auto view = registry.view<a_component, another_component>();
registry.destroy(view.begin(), view.end());
```
In both cases, the `create` member function accepts also a list of default
In all cases, the `create` member function accepts also a list of default
constructible component types to assign to the entities before to return. It's a
faster alternative to the creation and subsequent assignment of components in
separate steps.
faster alternative to the creation and subsequent assignment of components.
When an entity is destroyed, the registry can freely reuse it internally with a
slightly different identifier. In particular, the version of an entity is
increased each and every time it's discarded.<br/>
In case entity identifiers are stored around, the registry offers all the
functionalities required to test them and to get out of them the information
they carry:
increased after destruction.<br/>
Users can probe an identifier to know the information it carries:
```cpp
// returns true if the entity is still valid, false otherwise
@@ -217,12 +192,11 @@ auto version = registry.version(entity);
auto curr = registry.current(entity);
```
Components can be assigned to or removed from entities at any time with a few
calls to member functions of the registry. As for the entities, the registry
offers also a set of functionalities users can use to work with the components.
Components can be assigned to or removed from entities at any time. As for the
entities, the registry offers a set of functions to use to work with components.
The `assign` member function template creates, initializes and assigns to an
entity the given component. It accepts a variable number of arguments to
entity the given component. It accepts a variable number of arguments to use to
construct the component itself if present:
```cpp
@@ -251,10 +225,8 @@ vel.dx = 0.;
vel.dy = 0.;
```
In case users want to assign a component to an entity, but it's unknown whether
the entity already has it or not, `assign_or_replace` does the work in a single
call (there is a performance penalty to pay for this mainly due to the fact that
it has to check if the entity already has the given component or not):
When it's unknown whether an entity already owns an instance of a component,
`assign_or_replace` is the function to use instead:
```cpp
registry.assign_or_replace<position>(entity, 0., 0.);
@@ -266,8 +238,7 @@ vel.dx = 0.;
vel.dy = 0.;
```
Note that `assign_or_replace` is a slightly faster alternative for the following
`if/else` statement and nothing more:
This is a slightly faster alternative for the following snippet:
```cpp
if(registry.has<comp>(entity)) {
@@ -277,25 +248,23 @@ if(registry.has<comp>(entity)) {
}
```
As already shown, if in doubt about whether or not an entity has one or more
components, the `has` member function template may be useful:
The `has` member function may also be useful if in doubt about whether or not an
entity has one or more components:
```cpp
bool b = registry.has<position, velocity>(entity);
```
On the other side, if the goal is to delete a single component, the `remove`
member function template is the way to go when it's certain that the entity owns
a copy of the component:
If the goal is to delete a single component from an entity that owns it, the
`remove` member function template is the way to go:
```cpp
registry.remove<position>(entity);
```
Otherwise consider to use the `reset` member function. It behaves similarly to
`remove` but with a strictly defined behavior (and a performance penalty is the
price to pay for this). In particular it removes the component if and only if it
exists, otherwise it returns safely to the caller:
When in doubt whether the entity owns the component or not, use the `reset`
member function instead. It behaves similarly to `remove` but it discards the
component if and only if it exists, otherwise it returns safely to the caller:
```cpp
registry.reset<position>(entity);
@@ -317,7 +286,7 @@ There exist also two other _versions_ of the `reset` member function:
registry.reset();
```
Finally, references to components can be retrieved simply by doing this:
Finally, references to components can be retrieved simply as:
```cpp
const auto &cregistry = registry;
@@ -364,7 +333,7 @@ registry.on_construct<position>().disconnect<&my_class::member>(instance);
To be notified when components are destroyed, use the `on_destroy` member
function instead. Finally, the `on_replace` member function will return a sink
to which to connect listeners to observe changes on components.
to which to connect listeners to observe changes.
The function type of a listener for the construction signal should be equivalent
to the following:
@@ -373,10 +342,9 @@ to the following:
void(entt::entity, entt::registry &, Component &);
```
Where `Component` is intuitively the type of component of interest. In other
words, a listener is provided with the registry that triggered the notification
and the entity affected by the change, in addition to the newly created
instance.<br/>
Where `Component` is intuitively the type of component of interest. The listener
is provided with the registry that triggered the notification, the entity
affected by the change and the newly created instance.<br/>
The sink returned by the `on_replace` member function accepts listeners the
signature of which is the same of that of the construction signal. The one of
the destruction signal is also similar, except for the `Component` parameter:
@@ -387,8 +355,7 @@ void(entt::entity, entt::registry &);
This is mainly due to performance reasons. While the component is made available
after the construction, it is not when destroyed. Because of that, there are no
reasons to get it from the underlying storage unless the user requires so. In
this case, the registry is made available for the purpose.
reasons to get it from the underlying storage unless the user requires so.
Note also that:
@@ -402,10 +369,7 @@ Note also that:
* Listeners for the destruction signal are invoked **before** components have
been removed from entities.
* The order of invocation of the listeners isn't guaranteed in any case.
There are also some limitations on what a listener can and cannot do. In
particular:
There are also some limitations on what a listener can and cannot do:
* Connecting and disconnecting other functions from within the body of a
listener should be avoided. It can lead to undefined behavior in some cases.
@@ -418,14 +382,12 @@ particular:
To a certain extent, these limitations don't apply. However, it's risky to try
to force them and users should respect the limitations unless they know exactly
what they are doing. Subtle bugs are the price to pay in case of errors
otherwise.
what they are doing.
In general, events and therefore listeners must not be used as replacements for
systems. They should not contain much logic and interactions with a registry
should be kept to a minimum, if possible. Note also that the greater the number
of listeners, the greater the performance hit when components are created or
destroyed.
Events and therefore listeners must not be used as replacements for systems.
They shouldn't contain much logic and interactions with a registry should be
kept to a minimum. Moreover, the greater the number of listeners, the greater
the performance hit when components are created or destroyed.
Please, refer to the documentation of the signal class to know all the features
it offers.<br/>
@@ -435,11 +397,9 @@ list of parameters that is shorter than that of the signal itself.
### They call me Reactive System
As mentioned above, signals are the basic tools to construct reactive systems,
even if they are not enough on their own.<br/>
`EnTT` tries to take another step in that direction with the `observer` class
template.
Signals are the basic tools to construct reactive systems, even if they aren't
enough on their own. `EnTT` tries to take another step in that direction with
the `observer` class template.<br/>
In order to explain what reactive systems are, this is a slightly revised quote
from the documentation of the library that first introduced this tool,
[Entitas](https://github.com/sschmid/Entitas-CSharp):
@@ -456,19 +416,18 @@ end. The rules of language and the design of the library obviously impose and
allow different things.
An `observer` is initialized with an instance of a registry and a set of rules
that describe what are the entities to intercept. As an example:
that describes what are the entities to intercept. As an example:
```cpp
entt::observer observer{registry, entt::collector.replace<sprite>()};
```
The class is default constructible if required and it can be reconfigured at any
time by means of the `connect` member function. Moreover, instances can be
disconnected from the underlying registries through the `disconnect` member
function.<br/>
The `observer` offers also some member functions to query its internal state and
to know if it's empty or how many entities it contains. Moreover, it can return
a raw pointer to the list of entities it contains.
The class is default constructible and can be reconfigured at any time by means
of the `connect` member function. Moreover, instances can be disconnected from
the underlying registries through the `disconnect` member function.<br/>
The `observer` offers also what is needed to query the internal state and to
know if it's empty or how many entities it contains. Moreover, it can return a
raw pointer to the list of entities it contains.
However, the most important features of this class are that:
@@ -490,7 +449,7 @@ for(const auto entity: observer) {
observer.clear();
```
Note that the snippet above is equivalent to the following:
The snippet above is equivalent to the following:
```cpp
observer.each([](const auto entity) {
@@ -528,8 +487,8 @@ Roughly speaking, an observing matcher intercepts the entities for which the
given components are replaced (as in `registry::replace`) while a grouping
matcher tracks the entities that have assigned the given components since the
last time one asked.<br/>
Note that, for a grouping matcher, if an entity already has all the components
except one and the missing type is assigned to it, it is intercepted.
If an entity already has all the components except one and the missing type is
assigned to it, the entity is intercepted by a grouping matcher.
In addition, a matcher can be filtered with a `where` clause:
@@ -552,16 +511,14 @@ one.
## Sorting: is it possible?
It goes without saying that sorting entities and components is possible with
`EnTT`.<br/>
In fact, there are two functions that respond to slightly different needs:
Sorting entities and components is possible with `EnTT`.<br/>
There are two functions that respond to slightly different needs:
* Components can be sorted either directly:
```cpp
registry.sort<renderable>([](const auto &lhs, const auto &rhs) {
return lhs.z < rhs.z;
});
```
@@ -573,12 +530,9 @@ In fact, there are two functions that respond to slightly different needs:
});
```
There exists also the possibility to use a custom sort function object, as
long as it adheres to the requirements described in the inline
documentation.<br/>
This is possible mainly because users can get much more with a custom sort
function object if the usage pattern is known. As an example, in case of an
almost sorted pool, quick sort could be much, much slower than insertion sort.
There exists also the possibility to use a custom sort function object for
when the usage pattern is known. As an example, in case of an almost sorted
pool, quick sort could be much slower than insertion sort.
* Components can be sorted according to the order imposed by another component:
@@ -589,10 +543,8 @@ In fact, there are two functions that respond to slightly different needs:
In this case, instances of `movement` are arranged in memory so that cache
misses are minimized when the two components are iterated together.
As a side note, when groups are involved, the sorting functions are applied
separately to the elements that are part of the group and to those that are not,
effectively generating two partitions, both of which can be ordered
independently of each other.
As a side note, the use of groups limits the possibility of sorting pools of
components. Refer to the specific documentation for more details.
## Helpers
@@ -602,17 +554,15 @@ The list of helpers will grow longer as time passes and new ideas come out.
### Null entity
In `EnTT`, there exists a sort of _null entity_ made available to users that is
accessible via the `entt::null` variable.<br/>
In `EnTT`, the `entt::null` variable models the concept of _null entity_.<br/>
The library guarantees that the following expression always returns false:
```cpp
registry.valid(entt::null);
```
In other terms, a registry will reject the null entity in all cases because it
isn't considered valid. It means that the null entity cannot own components for
obvious reasons.<br/>
A registry rejects the null entity in all cases because it isn't considered
valid. It also means that the null entity cannot own components.<br/>
The type of the null entity is internal and should not be used for any purpose
other than defining the null entity itself. However, there exist implicit
conversions from the null entity to identifiers of any allowed type:
@@ -642,23 +592,21 @@ which entity belongs to which scene.<br/>
In fact, with `EnTT` this is even a recommended practice, as the registry is
nothing more than an opaque container you can swap at any time.
Once there are multiple registries available, however, one or more methods are
needed to transfer information from one container to another. This results in
the `stamp` member functions of the `registry` class .<br/>
These functions allow to take one entity from a registry and use it to _stamp_
one or more entities in another registry (or even the same, actually making
local copies).
Once there are multiple registries available, one or more methods are needed to
transfer information from one container to another. This results in the `stamp`
member functions of the `registry` class.<br/>
This function takes one entity from a registry and uses it to _stamp_ one or
more entities in another registry (or even the same, making local copies).
These features open definitely the doors to a lot of interesting features like
migrating entities between registries, prototypes, shadow registry, prefabs,
shared components without explicit ownership and copy-on-write policies among
It opens definitely the doors to a lot of interesting features like migrating
entities between registries, prototypes, shadow registry, prefabs, shared
components without explicit ownership models and copy-on-write policies among
the other things.
### Dependencies
The `registry` class is designed to create short circuits between its functions
within certain limits. This allows to easily define dependencies between
different operations.<br/>
The `registry` class is designed to create short circuits between its functions.
This makes easy to define dependencies between different operations.<br/>
For example, the following adds (or replaces) the component `a_type` whenever
`my_type` is assigned to an entity:
@@ -681,9 +629,8 @@ A dependency can also be easily broken as follows:
registry.on_construct<my_type>().disconnect<&entt::registry::assign_or_replace<a_type>>(registry);
```
There are many other types of dependencies besides those shown above. In
general, all functions that accept an entity as the first argument are good
candidates for this purpose.
There are many other types of dependencies. In general, all functions that
accept an entity as the first argument are good candidates for this purpose.
### Tags
@@ -706,8 +653,8 @@ working with components or for those who are migrating a project and want to
approach it one step at a time.
This class acts as a thin wrapper for an entity and for all its components. It's
constructed with a registry to be used behind the scenes and is in charge of the
destruction of the entity when it goes out of the scope.<br/>
constructed with a registry and is in charge of the destruction of the entity
when it goes out of the scope.<br/>
An actor offers all the functionalities required to work with components, such
as the `assign` and` remove` member functions, but also `has`,` get`, `try_get`
and so on.
@@ -723,9 +670,9 @@ groups, rather than with a tool that could introduce a performance degradation.
It is often convenient to assign context variables to a registry, so as to make
it the only _source of truth_ of an application.<br/>
This is possible by means of a member function named `set` to use to create a
context variable from a given type. Later on, either `ctx` or `try_ctx` can be
used to retrieve the newly created instance and `unset` is there to literally
reset it if needed.
context variable from a given type. Either `ctx` or `try_ctx` can be used to
retrieve the newly created instance, while `unset` is meant to literally reset
the variable if needed.
Example of use:
@@ -749,8 +696,7 @@ The type of a context variable must be such that it's default constructible and
can be moved. The `set` member function either creates a new instance of the
context variable or overwrites an already existing one if any. The `try_ctx`
member function returns a pointer to the context variable if it exists,
otherwise it returns a null pointer. This fits well with the `if` statement with
initializer.
otherwise it returns a null pointer.
## Snapshot: complete vs continuous
@@ -808,7 +754,7 @@ consequence of a couple of design choices from the past and in the present:
* Furthermore, the registry makes heavy use of _type-erasure_ techniques
internally and doesn't know at any time what component types it contains.
Therefore being explicit at the call point is mandatory.
Therefore being explicit at the call site is mandatory.
There exists also another version of the `component` member function that
accepts a range of entities to serialize. This version is a bit slower than the
@@ -867,8 +813,8 @@ The `orphans` member function literally destroys those entities that have no
components attached. It's usually useless if the snapshot is a full dump of the
source. However, in case all the entities are serialized but only few components
are saved, it could happen that some of the entities have no components once
restored. The best users can do to deal with them is to destroy those entities
and thus update their versions.
restored. The best the users can do to deal with them is to destroy those
entities and thus update their versions.
### Continuous loader
@@ -991,67 +937,39 @@ the best way to do it. However, feel free to use it at your own risk.
The basic idea is to store everything in a group of queues in memory, then bring
everything back to the registry with different loaders.
## Runtime components
Defining components at runtime is useful to support plugin systems and mods in
general. However, it seems impossible with a tool designed around a bunch of
templates. Indeed it's not that difficult.<br/>
Of course, some features cannot be easily exported into a runtime environment.
As an example, sorting a group of components defined at runtime isn't for free
if compared to most of the other operations. However, the basic functionalities
of an entity-component system such as `EnTT` fit the problem perfectly and can
also be used to manage runtime components if required.<br/>
All that is necessary to do it is to know the identifiers of the components. An
identifier is nothing more than a number or similar that can be used at runtime
to work with the type system.
In `EnTT`, identifiers are easily accessible:
```cpp
// component identifier
const auto type = entt::type_info<position>::id();
```
Once the identifiers are made available, almost everything becomes pretty
simple.
# Views and Groups
First of all, it is worth answering an obvious question: why views and
groups?<br/>
Briefly, they are a good tool to enforce single responsibility. A system that
has access to a registry can create and destroy entities, as well as assign and
First of all, it's worth answering a question: why views and groups?<br/>
Briefly, they're a good tool to enforce single responsibility. A system that has
access to a registry can create and destroy entities, as well as assign and
remove components. On the other side, a system that has access to a view or a
group can only iterate entities and their components, then read or update the
data members of the latter.<br/>
group can only iterate, read and update entities and components.<br/>
It is a subtle difference that can help designing a better software sometimes.
More in details, views are a non-intrusive tool to access entities and
components without affecting other functionalities or increasing the memory
consumption. On the other side, groups are an intrusive tool that allows to
reach higher performance along critical paths but has also a price to pay for
that.
More in details:
* Views are a non-intrusive tool to access entities and components without
affecting other functionalities or increasing the memory consumption.
* Groups are an intrusive tool that allows to reach higher performance along
critical paths but has also a price to pay for that.
There are mainly two kinds of views: _compile-time_ (also known as `view`) and
runtime (also known as `runtime_view`).<br/>
The former require that users indicate at compile-time what are the components
involved and can make several optimizations because of that. The latter can be
constructed at runtime instead and are a bit slower to iterate entities and
components.<br/>
In both cases, creating and destroying a view isn't expensive at all because
views don't have any type of initialization. Moreover, views don't affect any
other functionality of the registry and keep memory usage at a minimum.
The former requires a compile-time list of component types and can make several
optimizations because of that. The latter can be constructed at runtime instead
using numerical type identifiers and are a bit slower to iterate.<br/>
In both cases, creating and destroying a view isn't expensive at all since they
don't have any type of initialization.
Groups come in three different flavors: _full-owning groups_, _partial-owning
groups_ and _non-owning groups_. The main difference between them is in terms of
performance.<br/>
Groups can literally _own_ one or more component types. It means that they will
be allowed to rearrange pools so as to speed up iterations. Roughly speaking:
the more components a group owns, the faster it is to iterate them. On the other
side, a given component can belong to multiple groups only if they are _nested_,
so users have to define groups carefully to get the best out of them.
Continue reading for more details or refer to the inline documentation.
Groups can literally _own_ one or more component types. They are allowed to
rearrange pools so as to speed up iterations. Roughly speaking: the more
components a group owns, the faster it is to iterate them.<br/>
A given component can belong to multiple groups only if they are _nested_, so
users have to define groups carefully to get the best out of them.
## Views
@@ -1074,11 +992,10 @@ Multi component views iterate entities that have at least all the given
components in their bags. During construction, these views look at the number of
entities available for each component and pick up a reference to the smallest
set of candidates in order to speed up iterations.<br/>
They offer fewer functionalities than their companion views for single
component. In particular, a multi component view exposes utility functions to
get the estimated number of entities it is going to return and to know whether
it's empty or not. It's also possible to ask a view if it contains a given
entity.<br/>
They offer fewer functionalities than single component views. In particular,
a multi component view exposes utility functions to get the estimated number of
entities it is going to return and to know whether it's empty or not. It's also
possible to ask a view if it contains a given entity.<br/>
Refer to the inline documentation for all the details.
There is no need to store views around for they are extremely cheap to
@@ -1119,8 +1036,7 @@ for(auto entity: view) {
}
```
Or rely on the `each` member function to iterate entities and get all their
components at once:
Or rely on the `each` member function to iterate both entities and components:
```cpp
registry.view<position, velocity>().each([](auto entity, auto &pos, auto &vel) {
@@ -1132,9 +1048,8 @@ The `each` member function is highly optimized. Unless users want to iterate
only entities or get only some of the components, this should be the preferred
approach. Note that the entity can also be excluded from the parameter list if
not required, but this won't improve performance for multi component views.<br/>
There exists also an alternative version of `each` named `less` that works
exactly as its counterpart but for the fact that it doesn't return empty
components to the caller.
There exists an alternative version of `each` named `less` that works exactly as
its counterpart but for the fact that it doesn't return empty components.
As a side note, in the case of single component views, `get` accepts but doesn't
strictly require a template parameter, since the type is implicitly defined:
@@ -1148,9 +1063,8 @@ for(auto entity: view) {
}
```
**Note**: prefer the `get` member function of a view instead of the `get` member
function template of a registry during iterations, if possible. However, keep in
mind that it works only with the components of the view itself.
**Note**: prefer the `get` member function of a view instead of that of a
registry during iterations to get the types iterated by the view itself.
## Runtime views
@@ -1207,9 +1121,8 @@ views.
## Groups
Groups are meant to iterate multiple components at once and offer a (much)
faster alternative to views. Roughly speaking, they just play in another league
when compared to views.<br/>
Groups are meant to iterate multiple components at once and to offer a faster
alternative to multi component views.<br/>
Groups overcome the performance of the other tools available but require to get
the ownership of components and this sets some constraints on pools. On the
other side, groups aren't an automatism that increases memory consumption,
@@ -1233,7 +1146,7 @@ This is due to the fact that they must _observe_ changes in the pools of
interest and arrange data _correctly_ when needed for the types they own.<br/>
That being said, the way groups operate is beyond the scope of this document.
However, it's unlikely that users will be able to appreciate the impact of
groups on other functionalities of the registry.
groups on the other functionalities of a registry.
Groups offer a bunch of functionalities to get the number of entities they are
going to return and a raw access to the entity list as well as to the component
@@ -1266,8 +1179,7 @@ for(auto entity: group) {
}
```
Or rely on the `each` member function to iterate entities and get all their
components at once:
Or rely on the `each` member function to iterate both entities and components:
```cpp
registry.group<position>(entt::get<velocity>).each([](auto entity, auto &pos, auto &vel) {
@@ -1280,13 +1192,8 @@ only entities, this should be the preferred approach. Note that the entity can
also be excluded from the parameter list if not required and it can improve even
further the performance during iterations.
**Note**: prefer the `get` member function of a group instead of the `get`
member function template of a registry during iterations, if possible. However,
keep in mind that it works only with the components of the group itself.
Let's go a bit deeper into the different types of groups made available by this
library to know how they are constructed and what are the differences between
them.
**Note**: prefer the `get` member function of a group instead of that of a
registry during iterations to get the types iterated by the group itself.
### Full-owning groups
@@ -1294,7 +1201,7 @@ A full-owning group is the fastest tool an user can expect to use to iterate
multiple components at once. It iterates all the components directly, no
indirection required. This type of groups performs more or less as if users are
accessing sequentially a bunch of packed arrays of components all sorted
identically.
identically, with no jumps nor branches.
A full-owning group is created as:
@@ -1309,14 +1216,11 @@ auto group = registry.group<position, velocity>(entt::exclude<renderable>);
```
Once created, the group gets the ownership of all the components specified in
the template parameter list and arranges their pools so as to iterate all of
them as fast as possible.
the template parameter list and arranges their pools as needed.
Sorting owned components is no longer allowed once the group has been created.
However, full-owning groups can be sorted by means of their `sort` member
functions, if required. Sorting a full-owning group affects all the instances of
the same group (it means that users don't have to call `sort` on each instance
to sort all of them because they share the underlying data structure).
functions. Sorting a full-owning group affects all its instances.
### Partial-owning groups
@@ -1339,16 +1243,12 @@ auto group = registry.group<position>(entt::get<velocity>, entt::exclude<rendera
```
Once created, the group gets the ownership of all the components specified in
the template parameter list and arranges their pools so as to iterate all of
them as fast as possible. The ownership of the types provided via `entt::get`
doesn't pass to the group instead.
the template parameter list and arranges their pools as needed. The ownership of
the types provided via `entt::get` doesn't pass to the group instead.
Sorting owned components is no longer allowed once the group has been created.
However, partial-owning groups can be sorted by means of their `sort` member
functions, if required. Sorting a partial-owning group affects all the instances
of the same group (it means that users don't have to call `sort` on each
instance to sort all of them because they share the underlying data
structure).
functions. Sorting a partial-owning group affects all its instances.
### Non-owning groups
@@ -1371,13 +1271,10 @@ auto group = registry.group<>(entt::get<position, velocity>, entt::exclude<rende
The group doesn't receive the ownership of any type of component in this
case. This type of groups is therefore the least performing in general, but also
the only one that can be used in any situation to improve a performance where
necessary.
the only one that can be used in any situation to slightly improve performance.
Non-owning groups can be sorted by means of their `sort` member functions, if
required. Sorting a non-owning group affects all the instance of the same group
(it means that users don't have to call `sort` on each instance to sort all of
them because they share the set of entities).
Non-owning groups can be sorted by means of their `sort` member functions.
Sorting a non-owning group affects all its instances.
### Nested groups
@@ -1393,13 +1290,12 @@ also called _nested groups_, such as:
* `registry.group<sprite, transform, rotation>()`.
Fortunately, these are also very common cases if not the most common ones.<br/>
This allows users to have the highest possible performance on a greater number
of component combinations.
It allows to increase performance on a greater number of component combinations.
Two nested groups are such that they own at least one type of component and the
list of component types involved by one of them is contained entirely in that of
the other. More specifically, this applies independently to all component lists
used to define a group.<br/>
Two nested groups are such that they own at least one componet type and the list
of component types involved by one of them is contained entirely in that of the
other. More specifically, this applies independently to all component lists used
to define a group.<br/>
Therefore, the rules for defining whether two or more groups are nested can be
summarized as:
@@ -1410,25 +1306,24 @@ summarized as:
contains entirely that of the others. This also applies to the list of
observed and excluded components.
It means that more nested groups _extend_ the their parents by adding more
conditions in the form of new components.
It means that nested groups _extend_ their parents by adding more conditions in
the form of new components.
As mentioned, the components don't necessarily have to be all _owned_ so that
two groups can be considered nested. In other words, the following definitions
are fully valid:
two groups can be considered nested. The following definitions are fully valid:
* `registry.group<sprite>(entt::get<renderable>)`.
* `registry.group<sprite, transform>(entt::get<renderable>)`.
* `registry.group<sprite, transform>(entt::get<renderable, rotation>)`.
Exclusion lists also play their part in this respect. When it comes to defining
nested groups, an excluded type of component `T` is treated as being an observed
type `not_T`. Therefore, these two definitions:
nested groups, an excluded component type `T` is treated as being an observed
type `not_T`. Therefore, consider these two definitions:
* `registry.group<sprite, transform>()`.
* `registry.group<sprite, transform>(entt::exclude<rotation>)`.
Are treated as if users were defining the following groups:
They are treated as if users were defining the following groups:
* `group<sprite, transform>()`.
* `group<sprite, transform>(entt::get<not_rotation>)`.
@@ -1445,7 +1340,7 @@ expressed.<br/>
Note that the greater the number of component types involved by a group, the
more restrictive it is.
Despite the extreme flexibility of nested groups, which allow to independently
Despite the extreme flexibility of nested groups which allow to independently
use component types either owned, observed or excluded, the real strength of
this tool lies in the possibility of defining a greater number of groups that
**own** the same components, thus offering the best performance in more
@@ -1467,9 +1362,8 @@ The `registry` class offers two overloads when it comes to constructing views
and groups: a const version and a non-const one. The former accepts both const
and non-const types as template parameters, the latter accepts only const types
instead.<br/>
It means that views and groups can be constructed also from a const registry and
they propagate the constness of the registry to the types involved. As an
example:
It means that views and groups can be constructed from a const registry and they
propagate the constness of the registry to the types involved. As an example:
```cpp
entt::view<const position, const velocity> view = std::as_const(registry).view<const position, const velocity>();
@@ -1484,7 +1378,7 @@ entt::view<position, const velocity> view = registry.view<position, const veloci
In the example above, `view` can be used to access either read-only or writable
`position` components while `velocity` components are read-only in all
cases.<br/>
In other terms, these statements are all valid:
Similarly, these statements are all valid:
```cpp
position &pos = view.get<position>(entity);
@@ -1503,8 +1397,7 @@ std::tuple<position &, velocity &> tup = view.get<position, velocity>(entity);
std::tuple<const position &, velocity &> ctup = view.get<const position, velocity>(entity);
```
Similarly, the `each` member functions will propagate constness to the type of
the components returned during iterations:
The `each` member functions also propagates constness to its _return values_:
```cpp
view.each([](auto entity, position &pos, const velocity &vel) {
@@ -1512,9 +1405,8 @@ view.each([](auto entity, position &pos, const velocity &vel) {
});
```
Obviously, a caller can still refer to the `position` components through a const
reference because of the rules of the language that fortunately already allow
it.
A caller can still refer to the `position` components through a const reference
because of the rules of the language that fortunately already allow it.
The same concepts apply to groups as well.
@@ -1532,8 +1424,7 @@ registry.each([](auto entity) {
});
```
It returns to the caller all the entities that are still in use by means of the
given function.<br/>
It returns to the caller all the entities that are still in use.<br/>
As a rule of thumb, consider using a view or a group if the goal is to iterate
entities that have a determinate set of components. These tools are usually much
faster than combining this function with a bunch of custom tests.<br/>
@@ -1564,8 +1455,7 @@ Most of the _ECS_ available out there don't allow to create and destroy entities
and components during iterations.<br/>
`EnTT` partially solves the problem with a few limitations:
* Creating entities and components is allowed during iterations in almost all
cases.
* Creating entities and components is allowed during iterations in most cases.
* Deleting the current entity or removing its components is allowed during
iterations. For all the other entities, destroying them or removing their