doc: updated entity.md
This commit is contained in:
2
TODO
2
TODO
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user