First of all, thank you very much for taking the time to contribute to the EnTT framework.
- How to do it mostly depends on the type of contribution:
-
-
If you have a question, please ensure there isn't already an answer for you by searching on GitHub under issues. Do not forget to search also through the closed ones. If you are unable to find a proper answer, feel free to open a new issue. Usually, questions are marked as such and closed in a few days.
-
If you want to fix a typo in the inline documentation or in the README file, if you want to add some new sections or if you want to help me with the language by reviewing what I wrote so far (I'm not a native speaker after all), please open a new pull request with your changes.
-
If you found a bug, please ensure there isn't already an answer for you by searching on GitHub under issues. If you are unable to find an open issue addressing the problem, feel free to open a new one. Please, do not forget to carefully describe how to reproduce the problem, then add all the information about the system on which you are experiencing it and point out the version of EnTT you are using (tag or commit).
-
If you found a bug and you wrote a patch to fix it, open a new pull request with your code. Please, add some tests to avoid regressions in future if possible, it would be really appreciated. Note that the EnTT framework has a coverage at 100% (at least it was at 100% at the time I wrote this file) and this is the reason for which you can be confident with using it in a production environment.
-
If you want to propose a new feature and you know how to code it, please do not issue directly a pull request. Before to do it, create a new issue to discuss your proposal. Other users could be interested in your idea and the discussion that will follow can refine it and therefore give us a better solution.
-
If you want to request a new feature, I'm available for hiring. Take a look at my profile and feel free to write me.
-
-
+
+Introduction
+
EnTT comes with a bunch of core functionalities mostly used by the other parts of the library itself.
+ Hardly users will include these features in their code, but it's worth describing what EnTT offers so as not to reinvent the wheel in case of need.
+
+Compile-time identifiers
+
Sometimes it's useful to be able to give unique identifiers to types at compile-time.
+ There are plenty of different solutions out there and I could have used one of them. However, I decided to spend my time to define a compact and versatile tool that fully embraces what the modern C++ has to offer.
+
The result of my efforts is the identifier class template:
This is all what the class template has to offer: a type inline variable that contains a numerical identifier for the given type. It can be used in any context where constant expressions are required.
+
As long as the list remains unchanged, identifiers are also guaranteed to be the same for every run. In case they have been used in a production environment and a type has to be removed, one can just use a placeholder to left the other identifiers unchanged:
Sometimes it's useful to be able to give unique identifiers to types at runtime.
+ There are plenty of different solutions out there and I could have used one of them. In fact, I adapted the most common one to my requirements and used it extensively within the entire library.
+
It's the family class. Here is an example of use directly from the entity-component system:
This is all what a family has to offer: a type inline variable that contains a numerical identifier for the given type.
+
Please, note that identifiers aren't guaranteed to be the same for every run. Indeed it mostly depends on the flow of execution.
+
+Hashed strings
+
A hashed string is a zero overhead unique identifier. Users can use human-readable identifiers in the codebase while using their numeric counterparts at runtime, thus without affecting performance.
+ The class has an implicit constexpr constructor that chews a bunch of characters. Once created, all what one can do with it is getting back the original string or converting it into a number.
+ The good part is that a hashed string can be used wherever a constant expression is required and no string-to-number conversion will take place at runtime if used carefully.
There is also a user defined literal dedicated to hashed strings to make them more user-friendly:
+
constexpr auto str = "text"_hs;
+Wide characters
+
The hashed string has a design that is close to that of an std::basic_string. It means that hashed_string is nothing more than an alias for basic_hashed_string<char>. For those who want to use the C++ type for wide character representation, there exists also the alias hashed_wstring for basic_hashed_string<wchar_t>.
+ In this case, the user defined literal to use to create hashed strings on the fly is _hws:
+
constexpr auto str = "text"_hws;
Note that the hash type of the hashed_wstring is the same of its counterpart.
+
+Conflicts
+
The hashed string class uses internally FNV-1a to compute the numeric counterpart of a string. Because of the pigeonhole principle, conflicts are possible. This is a fact.
+ There is no silver bullet to solve the problem of conflicts when dealing with hashing functions. In this case, the best solution seemed to be to give up. That's all.
+ After all, human-readable unique identifiers aren't something strictly defined and over which users have not the control. Choosing a slightly different identifier is probably the best solution to make the conflict disappear in this case.
+
+Monostate
+
The monostate pattern is often presented as an alternative to a singleton based configuration system. This is exactly its purpose in EnTT. Moreover, this implementation is thread safe by design (hopefully).
+ Keys are represented by hashed strings, values are basic types like ints or bools. Values of different types can be associated to each key, even more than one at a time. Because of this, users must pay attention to use the same type both during an assignment and when they try to read back their data. Otherwise, they will probably incur in unexpected results.
EnTT comes with a bunch of core functionalities mostly used by the other parts of the library itself.
- Hardly users will include these features in their code, but it's worth describing what EnTT offers so as not to reinvent the wheel in case of need.
-
-Compile-time identifiers
-
Sometimes it's useful to be able to give unique identifiers to types at compile-time.
- There are plenty of different solutions out there and I could have used one of them. However, I decided to spend my time to define a compact and versatile tool that fully embraces what the modern C++ has to offer.
-
The result of my efforts is the identifier class template:
This is all what the class template has to offer: a type inline variable that contains a numerical identifier for the given type. It can be used in any context where constant expressions are required.
-
As long as the list remains unchanged, identifiers are also guaranteed to be the same for every run. In case they have been used in a production environment and a type has to be removed, one can just use a placeholder to left the other identifiers unchanged:
Sometimes it's useful to be able to give unique identifiers to types at runtime.
- There are plenty of different solutions out there and I could have used one of them. In fact, I adapted the most common one to my requirements and used it extensively within the entire library.
-
It's the family class. Here is an example of use directly from the entity-component system:
This is all what a family has to offer: a type inline variable that contains a numerical identifier for the given type.
-
Please, note that identifiers aren't guaranteed to be the same for every run. Indeed it mostly depends on the flow of execution.
-
-Hashed strings
-
A hashed string is a zero overhead unique identifier. Users can use human-readable identifiers in the codebase while using their numeric counterparts at runtime, thus without affecting performance.
- The class has an implicit constexpr constructor that chews a bunch of characters. Once created, all what one can do with it is getting back the original string or converting it into a number.
- The good part is that a hashed string can be used wherever a constant expression is required and no string-to-number conversion will take place at runtime if used carefully.
There is also a user defined literal dedicated to hashed strings to make them more user-friendly:
-
constexpr auto str = "text"_hs;
-Conflicts
-
The hashed string class uses internally FNV-1a to compute the numeric counterpart of a string. Because of the pigeonhole principle, conflicts are possible. This is a fact.
- There is no silver bullet to solve the problem of conflicts when dealing with hashing functions. In this case, the best solution seemed to be to give up. That's all.
- After all, human-readable unique identifiers aren't something strictly defined and over which users have not the control. Choosing a slightly different identifier is probably the best solution to make the conflict disappear in this case.
-
-Monostate
-
The monostate pattern is often presented as an alternative to a singleton based configuration system. This is exactly its purpose in EnTT. Moreover, this implementation is thread safe by design (hopefully).
- Keys are represented by hashed strings, values are basic types like ints or bools. Values of different types can be associated to each key, even more than one at a time. Because of this, users must pay attention to use the same type both during an assignment and when they try to read back their data. Otherwise, they will probably incur in unexpected results.
This is a constantly updated section where I'll try to put the answers to the most frequently asked questions.
- If you don't find your answer here, there are two cases: nobody has done it yet or this section needs updating. In both cases, try to open a new issue or enter the gitter channel and ask your question. Probably someone already has an answer for you and we can then integrate this part of the documentation.
-
-FAQ
-
-Why is my debug build on Windows so slow?
-
EnTT is an experimental project that I also use to keep me up-to-date with the latest revision of the language and the standard library. For this reason, it's likely that some classes you're working with are using standard containers under the hood.
- Unfortunately, it's known that the standard containers aren't particularly performing in debugging (the reasons for this go beyond this document) and are even less so on Windows apparently. Fortunately this can also be mitigated a lot, achieving good results in many cases.
-
First of all, there are two things to do in a Windows project:
-
-
Disable the /JMC option (Just My Code debugging), available starting in Visual Studio 2017 version 15.8.
-
Set the _ITERATOR_DEBUG_LEVEL macro to 0. This will disable checked iterators and iterator debugging.
-
-
Moreover, the macro ENTT_DISABLE_ASSERT should be defined to disable internal checks made by EnTT in debug. These are asserts introduced to help the users, but require to access to the underlying containers and therefore risk ruining the performance in some cases.
-
With these changes, debug performance should increase enough for most cases. If you want something more, you can can also switch to an optimization level O0 or preferably O1.
This is a constantly updated section where I'll try to put the answers to the most frequently asked questions.
+ If you don't find your answer here, there are two cases: nobody has done it yet or this section needs updating. In both cases, try to open a new issue or enter the gitter channel and ask your question. Probably someone already has an answer for you and we can then integrate this part of the documentation.
+
+FAQ
+
+Why is my debug build on Windows so slow?
+
EnTT is an experimental project that I also use to keep me up-to-date with the latest revision of the language and the standard library. For this reason, it's likely that some classes you're working with are using standard containers under the hood.
+ Unfortunately, it's known that the standard containers aren't particularly performing in debugging (the reasons for this go beyond this document) and are even less so on Windows apparently. Fortunately this can also be mitigated a lot, achieving good results in many cases.
+
First of all, there are two things to do in a Windows project:
+
+
Disable the /JMC option (Just My Code debugging), available starting in Visual Studio 2017 version 15.8.
+
Set the _ITERATOR_DEBUG_LEVEL macro to 0. This will disable checked iterators and iterator debugging.
+
+
Moreover, the macro ENTT_DISABLE_ASSERT should be defined to disable internal checks made by EnTT in debug. These are asserts introduced to help the users, but require to access to the underlying containers and therefore risk ruining the performance in some cases.
+
With these changes, debug performance should increase enough for most cases. If you want something more, you can can also switch to an optimization level O0 or preferably O1.
+
+How can I represent hierarchies with my components?
+
This is one of the first questions that anyone makes when starting to work with the entity-component-system architectural pattern.
+ There are several approaches to the problem and what’s the best one depends mainly on the real problem one is facing. In all cases, how to do it doesn't strictly depend on the library in use, but the latter can certainly allow or not different techniques depending on how the data are laid out.
+
I tried to describe some of the techniques that fit well with the model of EnTT. Here is the first post of a series that tries to explore the problem. More will probably come in future.
+
Long story short, you can always define a tree where the nodes expose implicit lists of children by means of the following type:
The sort functionalities of EnTT, the groups and all the other features of the library can help then to get the best in terms of data locality and therefore performance from this component.
+
+Custom entity identifiers: yay or nay?
+
Custom entity identifiers are definitely a good idea in two cases at least:
+
+
If std::uint32_t isn't large enough as an underlying type.
+
If you want to avoid conflicts when using multiple registries.
+
+
These identifiers are nothing more than enum classes with some salt.
+ To simplify the creation of new identifiers, EnTT provides the macro ENTT_OPAQUE_TYPE that accepts two arguments:
+
+
The name you want to give to the new identifier (watch out for namespaces).
+
The underlying type to use (either std::uint16_t, std::uint32_t or std::uint64_t).
+
+
In fact, this is the definition of entt::entity:
+
ENTT_OPAQUE_TYPE(entity, std::uint32_t)
The use of this macro is highly recommended, so as not to run into problems if the requirements for the identifiers should change in the future.
+
+Warning C4307: integral constant overflow
+
According to this issue, using a hashed string under VS could generate a warning.
+ First of all, I want to reassure you: it's expected and harmless. However, it can be annoying.
+
To suppress it and if you don't want to suppress all the other warnings as well, here is a workaround in the form of a macro:
+
#if defined(_MSC_VER)
#define HS(str)\
__pragma(warning(push))\
__pragma(warning(disable:4307))\
entt::hashed_string{str}\
__pragma(warning(pop))
#else
#define HS(str) entt::hashed_string{str}
#endif
With an example of use included:
+
constexpr auto identifier = HS("my/resource/identifier");
On Windows, a header file defines two macros min and max which may result in conflicts with their counterparts in the standard library and therefore in errors during compilation.
+
It's a pretty big problem but fortunately it's not a problem of EnTT and there is a fairly simple solution to it.
+ It consists in defining the NOMINMAX macro before to include any other header so as to get rid of the extra definitions:
EnTT has historically had a limit when used across boundaries on Windows in general and on GNU/Linux when default visibility was set to hidden. The limitation is due mainly to a custom utility used to assign unique, sequential identifiers to different types. Unfortunately, this tool is used by several core classes (the registry among the others) that are thus almost unusable across boundaries.
- The reasons for that are beyond the purposes of this document. However, the good news is that EnTT also offers now a way to overcome this limit and to push things across boundaries without problems when needed.
-
-Named types and traits class
-
To allow a type to work properly across boundaries when used by a class that requires to assign unique identifiers to types, users must specialize a class template to literally give a compile-time name to the type itself.
- The name of the class template is name_type_traits and the specialization must be such that it exposes a static constexpr data member named value having type either ENTT_ID_TYPE or entt::hashed_string::hash_type. Its value is the user defined unique identifier assigned to the specific type.
- Identifiers are not to be sequentially generated in this case.
Because of the rules of the language, the specialization must reside in the global namespace or in the entt namespace. There is no way to change this rule unfortunately, because it doesn't depend on the library itself.
-
The good aspect of this approach is that it's not intrusive at all. The other way around was in fact forcing users to inherit all their classes from a common base. Something to avoid, at least from my point of view.
- However, despite the fact that it's not intrusive, it would be great if it was also easier to use and a bit less error-prone. This is why a bunch of macros exist to ease defining named types.
-
-Do not mix types
-
Someone might think that this trick is valid only for the types to push across boundaries. This isn't how things work. In fact, the problem is more complex than that.
- As a rule of thumb, users should never mix named and non-named types. Whenever a type is given a name, all the types must be given a name. As an example, consider the registry class template: in case it is pushed across boundaries, all the types of components should be assigned a name to avoid subtle bugs.
-
Indeed, this constraint can be relaxed in many cases. However, it is difficult to define a general rule to follow that is not the most stringent, unless users know exactly what they are doing. Therefore, I won't elaborate on giving further details on the topic.
-
-Macros, macros everywhere
-
The library comes with a set of predefined macros to use to declare named types or export already existing ones. In particular:
-
-
ENTT_NAMED_TYPE can be used to assign a name to already existing types. This macro must be used in the global namespace even when the types to be named are not.
-
-
ENTT_NAMED_TYPE(my_type)
ENTT_NAMED_TYPE(ns::another_type)
-
ENTT_NAMED_STRUCT can be used to define and export a struct at the same time. It accepts also an optional namespace in which to define the given type. This macro must be used in the global namespace.
ENTT_NAMED_CLASS can be used to define and export a class at the same time. It accepts also an optional namespace in which to define the given type. This macro must be used in the global namespace.
-
-
ENTT_NAMED_CLASS(my_type, { /* class definition */})
ENTT_NAMED_CLASS(ns, another_type, { /* class definition */})
Nested namespaces are supported out of the box as well in all cases. As an example:
These macros can be used to avoid specializing the named_type_traits class template. In all cases, the name of the class is used also as a seed to generate the compile-time unique identifier.
-
-Conflicts
-
When using macros, unique identifiers are 32/64 bit integers generated by hashing strings during compilation. Therefore, conflicts are rare but still possible. In case of conflicts, everything simply will get broken at runtime and the strangest things will probably take place.
- Unfortunately, there is no safe way to prevent it. If this happens, it will be enough to give a different value to one of the conflicting types to solve the problem. To do this, users can either assign a different name to the class or directly define a specialization for the named_type_traits class template.
-
-Allocations: the dark side of the force
-
As long as EnTT won't support custom allocators, another problem with allocations will remain alive instead. This is in fact easily solved, or at least it is if one knows it.
-
To allow users to add types dynamically, the library makes extensive use of type erasure techniques and dynamic allocations for pools (whether they are for components, events or anything else). The problem occurs when, for example, a registry is created on one side of a boundary and a pool is dynamically created on the other side. In the best case, everything will crash at the exit, while at worst it will do so at runtime.
- To avoid problems, the pools must be generated from the same side of the boundary where the object that owns them is also created. As an example, when the registry is created in the main executable and used across boundaries for a given type of component, the pool for that type must be created before passing around the registry itself. To do this is fortunately quite easy, since it is sufficient to invoke any of the methods that involve the given type (continuing the example with the registry, a call to reserve or size is more than enough).
-
Maybe one day some dedicated methods will be added that do nothing but create a pool for a given type. Until now it has been preferred to keep the API cleaner as they are not strictly necessary.
-
-
-
-Generated by
-
- 1.8.13
-
-
-
diff --git a/autotoc_md57.html b/autotoc_md57.html
index 6e402c603..e34ce5385 100644
--- a/autotoc_md57.html
+++ b/autotoc_md57.html
@@ -5,7 +5,7 @@
-EnTT: EnTT in Action
+EnTT: Push EnTT across boundaries
@@ -22,7 +22,7 @@
EnTT
- 3.0.0
+ 3.1.0
@@ -60,55 +60,51 @@ $(function() {
-
EnTT in Action
+
Push EnTT across boundaries
-
EnTT is widely used in private and commercial applications. I cannot even mention most of them because of some signatures I put on some documents time ago. Fortunately, there are also people who took the time to implement open source projects based on EnTT and did not hold back when it came to documenting them.
-
Below an incomplete list of games, applications and articles that can be used as a reference. Where I put the word apparently means that the use of EnTT is documented but the authors didn't make explicit announcements or contacted me directly.
-
I hope this list can grow much more in the future:
+
+Introduction
+
EnTT has historically had a limit when used across boundaries on Windows in general and on GNU/Linux when default visibility was set to hidden. The limitation is due mainly to a custom utility used to assign unique, sequential identifiers to different types. Unfortunately, this tool is used by several core classes (the registry among the others) that are thus almost unusable across boundaries.
+ The reasons for that are beyond the purposes of this document. However, the good news is that EnTT also offers now a way to overcome this limit and to push things across boundaries without problems when needed.
+
+Named types and traits class
+
To allow a type to work properly across boundaries when used by a class that requires to assign unique identifiers to types, users must specialize a class template to literally give a compile-time name to the type itself.
+ The name of the class template is name_type_traits and the specialization must be such that it exposes a static constexpr data member named value having type either ENTT_ID_TYPE or entt::hashed_string::hash_type. Its value is the user defined unique identifier assigned to the specific type.
+ Identifiers are not to be sequentially generated in this case.
Because of the rules of the language, the specialization must reside in the global namespace or in the entt namespace. There is no way to change this rule unfortunately, because it doesn't depend on the library itself.
+
The good aspect of this approach is that it's not intrusive at all. The other way around was in fact forcing users to inherit all their classes from a common base. Something to avoid, at least from my point of view.
+ However, despite the fact that it's not intrusive, it would be great if it was also easier to use and a bit less error-prone. This is why a bunch of macros exist to ease defining named types.
+
+Do not mix types
+
Someone might think that this trick is valid only for the types to push across boundaries. This isn't how things work. In fact, the problem is more complex than that.
+ As a rule of thumb, users should never mix named and non-named types. Whenever a type is given a name, all the types must be given a name. As an example, consider the registry class template: in case it is pushed across boundaries, all the types of components should be assigned a name to avoid subtle bugs.
+
Indeed, this constraint can be relaxed in many cases. However, it is difficult to define a general rule to follow that is not the most stringent, unless users know exactly what they are doing. Therefore, I won't elaborate on giving further details on the topic.
+
+Macros, macros everywhere
+
The library comes with a set of predefined macros to use to declare named types or export already existing ones. In particular:
-
Games:
-
Minecraft by Mojang: of course, that Minecraft, see the open source attributions page for more details.
EnTT Breakout: simple example of a breakout game, using SDL and EnTT.
+
ENTT_NAMED_TYPE can be used to assign a name to already existing types. This macro must be used in the global namespace even when the types to be named are not.
ENTT_NAMED_STRUCT can be used to define and export a struct at the same time. It accepts also an optional namespace in which to define the given type. This macro must be used in the global namespace.
-
-
Emulators:
-
NovusCore: A modern take on World of Warcraft emulation.
ENTT_NAMED_CLASS can be used to define and export a class at the same time. It accepts also an optional namespace in which to define the given type. This macro must be used in the global namespace.
-
-
Articles and blog posts
-
Some posts on my personal blog are about EnTT, for those who want to know more on this project.
The ArcGIS Runtime SDKs by Esri: they use EnTT for the internal ECS and the cross platform C++ rendering engine. The SDKs are utilized by a lot of enterprise custom apps, as well as by Esri for its own public applications such as Explorer, Collector and Navigator.
-
ApparentlyNIO: there was a collaboration to make some changes to EnTT, at the time used for internal projects.
These macros can be used to avoid specializing the named_type_traits class template. In all cases, the name of the class is used also as a seed to generate the compile-time unique identifier.
+
+Conflicts
+
When using macros, unique identifiers are 32/64 bit integers generated by hashing strings during compilation. Therefore, conflicts are rare but still possible. In case of conflicts, everything simply will get broken at runtime and the strangest things will probably take place.
+ Unfortunately, there is no safe way to prevent it. If this happens, it will be enough to give a different value to one of the conflicting types to solve the problem. To do this, users can either assign a different name to the class or directly define a specialization for the named_type_traits class template.
+
+Allocations: the dark side of the force
+
As long as EnTT won't support custom allocators, another problem with allocations will remain alive instead. This is in fact easily solved, or at least it is if one knows it.
+
To allow users to add types dynamically, the library makes extensive use of type erasure techniques and dynamic allocations for pools (whether they are for components, events or anything else). The problem occurs when, for example, a registry is created on one side of a boundary and a pool is dynamically created on the other side. In the best case, everything will crash at the exit, while at worst it will do so at runtime.
+ To avoid problems, the pools must be generated from the same side of the boundary where the object that owns them is also created. As an example, when the registry is created in the main executable and used across boundaries for a given type of component, the pool for that type must be created before passing around the registry itself. To do this is fortunately quite easy, since it is sufficient to invoke any of the methods that involve the given type (continuing the example with the registry, a call to reserve or size is more than enough).
+
Maybe one day some dedicated methods will be added that do nothing but create a pool for a given type. Until now it has been preferred to keep the API cleaner as they are not strictly necessary.
EnTT is widely used in private and commercial applications. I cannot even mention most of them because of some signatures I put on some documents time ago. Fortunately, there are also people who took the time to implement open source projects based on EnTT and did not hold back when it came to documenting them.
+
Below an incomplete list of games, applications and articles that can be used as a reference. Where I put the word apparently means that the use of EnTT is documented but the authors didn't make explicit announcements or contacted me directly.
+
I hope this list can grow much more in the future:
+
+
Games:
+
Minecraft by Mojang: of course, that Minecraft, see the open source attributions page for more details.
The ArcGIS Runtime SDKs by Esri: they use EnTT for the internal ECS and the cross platform C++ rendering engine. The SDKs are utilized by a lot of enterprise custom apps, as well as by Esri for its own public applications such as Explorer, Collector and Navigator.
+
ApparentlyNIO: there was a collaboration to make some changes to EnTT, at the time used for internal projects.
+
ApparentlyTieto: they published a job post where EnTT was listed on their software stack.
GitHub contains also many other examples of use of EnTT from which to take inspiration if interested.
+
+
+
+
If you know of other resources out there that are about EnTT, feel free to open an issue or a PR and I'll be glad to add them to this page.
+
+
+
+Generated by
+
+ 1.8.13
+
+
+
diff --git a/autotoc_md58.html b/autotoc_md65.html
similarity index 97%
rename from autotoc_md58.html
rename to autotoc_md65.html
index 7f31e4b1e..d850ab3ac 100644
--- a/autotoc_md58.html
+++ b/autotoc_md65.html
@@ -22,7 +22,7 @@
EnTT
- 3.0.0
+ 3.1.0
@@ -63,11 +63,11 @@ $(function() {
Crash Course: service locator
-
+
Introduction
Usually service locators are tightly bound to the services they expose and it's hard to define a general purpose solution. This template based implementation tries to fill the gap and to get rid of the burden of defining a different specific locator for each application.
This class is tiny, partially unsafe and thus risky to use. Moreover it doesn't fit probably most of the scenarios in which a service locator is required. Look at it as a small tool that can sometimes be useful if users know how to handle it.
-
+
Service locator
The API is straightforward. The basic idea is that services are implemented by means of interfaces and rely on polymorphism.
The locator is instantiated with the base type of the service if any and a concrete implementation is provided along with all the parameters required to initialize it. As an example:
diff --git a/autotoc_md61.html b/autotoc_md68.html
similarity index 58%
rename from autotoc_md61.html
rename to autotoc_md68.html
index b04a4569a..791f331c1 100644
--- a/autotoc_md61.html
+++ b/autotoc_md68.html
@@ -5,7 +5,7 @@
-EnTT: Crash Course: reflection system
+EnTT: Crash Course: runtime reflection system
@@ -22,7 +22,7 @@
EnTT
- 3.0.0
+ 3.1.0
@@ -60,67 +60,77 @@ $(function() {
-
Crash Course: reflection system
+
Crash Course: runtime reflection system
-
+
Introduction
Reflection (or rather, its lack) is a trending topic in the C++ world and, in the specific case of EnTT, a tool that can unlock a lot of other features. I looked for a third-party library that met my needs on the subject, but I always came across some details that I didn't like: macros, being intrusive, too many allocations. In one word: unsatisfactory.
I finally decided to write a built-in, non-intrusive and macro-free runtime reflection system for EnTT. Maybe I didn't do better than others or maybe yes, time will tell me, but at least I can model this tool around the library to which it belongs and not vice versa.
-
+
+Names and identifiers
+
The meta system doesn't force the user to use the tools provided by the library when it comes to working with names and identifiers. It does this by offering an API that works with opaque identifiers that may or may not be generated by means of a hashed string.
+ This means that users can assign any type of identifier to the meta objects, as long as they are numeric. It doesn't matter if they are generated at runtime, at compile-time or with custom functions.
+
However, the examples in the following sections are all based on the hashed_string class as provided by this library. Therefore, where an identifier is required, it's likely that a user defined literal is used as follows:
+
auto factory = entt::reflect<my_type>("reflected_type"_hs);
For what it's worth, this is likely completely equivalent to:
+
auto factory = entt::reflect<my_type>(42);
Obviously, human-readable identifiers are more convenient to use and highly recommended.
+
Reflection in a nutshell
Reflection always starts from real types (users cannot reflect imaginary types and it would not make much sense, we wouldn't be talking about reflection anymore).
To reflect a type, the library provides the reflect function:
-
auto factory = entt::reflect<my_type>("reflected_type");
It accepts the type to reflect as a template parameter and an optional name as an argument. Names are important because users can retrieve meta types at runtime by searching for them by name. However, there are cases in which users can be interested in adding features to a reflected type so that the reflection system can use it correctly under the hood, but they don't want to allow searching the type by name.
+
auto factory = entt::reflect<my_type>("reflected_type"_hs);
It accepts the type to reflect as a template parameter and an optional identifier as an argument. Identifiers are important because users can retrieve meta types at runtime by searching for them by name. However, there are cases in which users can be interested in adding features to a reflected type so that the reflection system can use it correctly under the hood, but they don't want to allow searching the type by name.
In both cases, the returned value is a factory object to use to continue building the meta type.
A factory is such that all its member functions returns the factory itself. It can be used to extend the reflected type and add the following:
Constructors. Actual constructors can be assigned to a reflected type by specifying their list of arguments. Free functions (namely, factories) can be used as well, as long as the return type is the expected one. From a client's point of view, nothing changes if a constructor is a free function or an actual constructor.
Use the ctor member function for this purpose:
Destructors. Free functions can be set as destructors of reflected types. The purpose is to give users the ability to free up resources that require special treatment before an object is actually destroyed.
Use the dtor member function for this purpose:
A function should neither delete nor explicitly invoke the destructor of a given instance.
+
Data members. Both real data members of the underlying type and static and global variables, as well as constants of any kind, can be attached to a meta type. From a client's point of view, all the variables associated with the reflected type will appear as if they were part of the type itself.
Use the data member function for this purpose:
-
entt::reflect<my_type>("reflected")
.data<&my_type::static_variable>("static")
.data<&my_type::data_member>("member")
.data<&global_variable>("global");
This function requires as an argument the name to give to the meta data once created. Users can then access meta data at runtime by searching for them by name.
+
entt::reflect<my_type>("reflected"_hs)
.data<&my_type::static_variable>("static"_hs)
.data<&my_type::data_member>("member"_hs)
.data<&global_variable>("global"_hs);
This function requires as an argument the identifier to give to the meta data once created. Users can then access meta data at runtime by searching for them by name.
Data members can be set also by means of a couple of functions, namely a setter and a getter. Setters and getters can be either free functions, member functions or mixed ones, as long as they respect the required signatures.
Refer to the inline documentation for all the details.
Member functions. Both real member functions of the underlying type and free functions can be attached to a meta type. From a client's point of view, all the functions associated with the reflected type will appear as if they were part of the type itself.
Use the func member function for this purpose:
-
entt::reflect<my_type>("reflected")
.func<&my_type::static_function>("static")
.func<&my_type::member_function>("member")
.func<&free_function>("free");
This function requires as an argument the name to give to the meta function once created. Users can then access meta functions at runtime by searching for them by name.
+
entt::reflect<my_type>("reflected"_hs)
.func<&my_type::static_function>("static"_hs)
.func<&my_type::member_function>("member"_hs)
.func<&free_function>("free"_hs);
This function requires as an argument the identifier to give to the meta function once created. Users can then access meta functions at runtime by searching for them by name.
Base classes. A base class is such that the underlying type is actually derived from it. In this case, the reflection system tracks the relationship and allows for implicit casts at runtime when required.
Use the base member function for this purpose:
From now on, wherever a base_type is required, an instance of derived_type will also be accepted.
Conversion functions. Actual types can be converted, this is a fact. Just think of the relationship between a double and an int to see it. Similar to bases, conversion functions allow users to define conversions that will be implicitly performed by the reflection system when required.
Use the conv member function for this purpose:
entt::reflect<double>().conv<int>();
That's all, everything users need to create meta types and enjoy the reflection system. At first glance it may not seem that much, but users usually learn to appreciate it over time.
Also, do not forget what these few lines hide under the hood: a built-in, non-intrusive and macro-free system for reflection in C++. Features that are definitely worth the price, at least for me.
-
-Any as in any type
+
+Any as in any type
The reflection system comes with its own meta any type. It may seem redundant since C++17 introduced std::any, but it is not.
In fact, the type returned by an std::any is a const reference to an std::type_info, an implementation defined class that's not something everyone wants to see in a software. Furthermore, the class std::type_info suffers from some design flaws and there is even no way to convert an std::type_info into a meta type, thus linking the two worlds.
A meta any object provides an API similar to that of its most famous counterpart and serves the same purpose of being an opaque container for any type of value.
It minimizes the allocations required, which are almost absent thanks to SBO techniques. In fact, unless users deal with fat types and create instances of them though the reflection system, allocations are at zero.
A meta any object can be created by any other object or as an empty container to initialize later:
It can be constructed or assigned by copy and move and it takes the burden of destroying the contained object when required.
- A meta any object has a type member function that returns the meta type of the contained value, if any. The member functions can_cast and can_convert are used to know if the underlying object has a given type as a base or if it can be converted implicitly to it. Similarly, cast and convert do what they promise and return the expected value.
It takes the burden of destroying the contained instance when required.
+ Moreover, it can be used as an opaque container for unmanaged objects if needed:
In this case, the contained instance is never destroyed and users must ensure that the lifetime of the object exceeds that of the container.
+
A meta any object has a type member function that returns the meta type of the contained value, if any. The member functions try_cast, cast and convert are used to know if the underlying object has a given type as a base or if it can be converted implicitly to it.
+
+Enjoy the runtime
Once the web of reflected types has been constructed, it's a matter of using it at runtime where required.
All this has the great merit that, unlike the vast majority of the things present in this library and closely linked to the compile-time, the reflection system stands in fact as a non-intrusive tool for the runtime.
-
To search for a reflected type there are two options: by type or by name. In both cases, the search can be done by means of the resolve function:
In all cases, the returned value is an instance of meta_type. This type of objects offer an API to know the runtime name of the type, to iterate all the meta objects associated with them and even to build or destroy instances of the underlying type.
+
To search for a reflected type there are two options: by type or by name. In both cases, the search can be done by means of the resolve function:
In all cases, the returned value is an instance of meta_type. This type of objects offer an API to know the runtime identifier of the type, to iterate all the meta objects associated with them and even to build or destroy instances of the underlying type.
Refer to the inline documentation for all the details.
The meta objects that compose a meta type are accessed in the following ways:
@@ -134,19 +144,19 @@ Enjoy the runtime
auto dtor = entt::resolve<my_type>().dtor();
The returned type is meta_dtor and may be invalid if there is no custom destructor set for the given meta type.
All what a meta destructor has to offer is a way to invoke it on a given instance. Be aware that the result may not be what is expected.
-
Meta data. They are accessed by name:
+
Meta data. They are accessed by name:
-
auto data = entt::resolve<my_type>().data("member");
The returned type is meta_data and may be invalid if there is no meta data object associated with the given name.
+
auto data = entt::resolve<my_type>().data("member"_hs);
The returned type is meta_data and may be invalid if there is no meta data object associated with the given identifier.
A meta data object offers an API to query the underlying type (ie to know if it's a const or a static one), to get the meta type of the variable and to set or get the contained value.
-
Meta functions. They are accessed by name:
+
Meta functions. They are accessed by name:
-
auto func = entt::resolve<my_type>().func("member");
The returned type is meta_func and may be invalid if there is no meta function object associated with the given name.
+
auto func = entt::resolve<my_type>().func("member"_hs);
The returned type is meta_func and may be invalid if there is no meta function object associated with the given identifier.
A meta function object offers an API to query the underlying type (ie to know if it's a const or a static function), to know the number of arguments, the meta return type and the meta types of the parameters. In addition, a meta function object can be used to invoke the underlying function and then get the return value in the form of meta any object.
-
Meta bases. They are accessed through the name of the base types:
+
Meta bases. They are accessed through the name of the base types:
-
auto base = entt::resolve<derived_type>().base("base");
The returned type is meta_base and may be invalid if there is no meta base object associated with the given name.
+
auto base = entt::resolve<derived_type>().base("base"_hs);
The returned type is meta_base and may be invalid if there is no meta base object associated with the given identifier.
Meta bases aren't meant to be used directly, even though they are freely accessible. They expose only a few methods to use to know the meta type of the base class and to convert a raw pointer between types.
Meta conversion functions. They are accessed by type:
@@ -154,30 +164,53 @@ Enjoy the runtime
auto conv = entt::resolve<double>().conv<int>();
The returned type is meta_conv and may be invalid if there is no meta conversion function associated with the given type.
The meta conversion functions are as thin as the meta bases and with a very similar interface. The sole difference is that they return a newly created instance wrapped in a meta any object when they convert between different types.
All the objects thus obtained as well as the meta types can be explicitly converted to a boolean value to check if they are valid:
-
auto func = entt::resolve<my_type>().func("member");
if(func) {
// ...
}
Furthermore, all meta objects with the exception of meta destructors can be iterated through an overload that accepts a callback through which to return them. As an example:
+
auto func = entt::resolve<my_type>().func("member"_hs);
if(func) {
// ...
}
Furthermore, all meta objects with the exception of meta destructors can be iterated through an overload that accepts a callback through which to return them. As an example:
entt::resolve<my_type>().data([](auto data) {
// ...
});
A meta type can also be used to construct or destroy actual instances of the underlying type.
- In particular, the construct member function accepts a variable number of arguments and searches for a match. It returns a meta_any object that may or may not be initialized, depending on whether a suitable constructor has been found or not. On the other side, the destroy member function accepts instances of meta_any as well as actual objects by reference and invokes the registered destructor if any or a default one.
- Be aware that the result of a call to destroy may not be what is expected.
+ In particular, the construct member function accepts a variable number of arguments and searches for a match. It returns a meta_any object that may or may not be initialized, depending on whether a suitable constructor has been found or not. On the other side, the destroy member function accepts instances of meta_any as well as actual objects by reference and invokes the registered destructor if any.
+ Be aware that the result of a call to destroy may not be what is expected. The purpose is to give users the ability to free up resources that require special treatment and not to actually destroy instances.
Meta types and meta objects in general contain much more than what is said: a plethora of functions in addition to those listed whose purposes and uses go unfortunately beyond the scope of this document.
I invite anyone interested in the subject to look at the code, experiment and read the official documentation to get the best out of this powerful tool.
-
-Named constants and enums
+
+Policies: the more, the less
+
Policies are a kind of compile-time directives that can be used when recording reflection information.
+ Their purpose is to require slightly different behavior than the default in some specific cases. For example, when reading a given data member, its value is returned wrapped in a meta_any object which, by default, makes a copy of it. For large objects or if the caller wants to access the original instance, this behavior isn't desirable. Policies are there to offer a solution to this and other problems.
+
There are a few alternatives available at the moment:
+
+
The as-is policy, associated with the type entt::as_is_t.
+ This is the default policy. In general, it should never be used explicitly, since it's implicitly selected if no other policy is specified.
+ In this case, the return values of the functions as well as the properties exposed as data members are always returned by copy in a dedicated wrapper and therefore associated with their original meta types.
+
The as-void policy, associated with the type entt::as_void_t.
+ Its purpose is to discard the return value of a meta object, whatever it is, thus making it appear as if its type were void.
+ If the use with functions is obvious, it must be said that it's also possible to use this policy with constructors and data members. In the first case, the constructor will be invoked but the returned wrapper will actually be empty. In the second case, instead, the property will not be accessible for reading.
The as-alias policy, associated with the type entt::as_alias_t.
+ It allows to build wrappers that act as aliases for the objects used to initialize them. Modifying the object contained in the wrapper for which the aliasing was requested will make it possible to directly modify the instance used to initialize the wrapper itself.
+ This policy works with constructors (for example, when objects are taken from an external container rather than created on demand), data members and functions in general (as long as their return types are lvalue references).
Some uses are rather trivial, but it's useful to note that there are some less obvious corner cases that can in turn be solved with the use of policies.
+
+Named constants and enums
A special mention should be made for constant values and enums. It wouldn't be necessary, but it will help distracted readers.
As mentioned, the data member function can be used to reflect constants of any type among the other things.
This allows users to create meta types for enums that will work exactly like any other meta type built from a class. Similarly, arithmetic types can be enriched with constants of special meaning where required.
Personally, I find it very useful not to export what is the difference between enums and classes in C++ directly in the space of the reflected types.
All the values thus exported will appear to users as if they were constant data members of the reflected types.
Exporting constant values or elements from an enum is as simple as ever:
-
entt::reflect<my_enum>()
.data<my_enum::a_value>("a_value")
.data<my_enum::another_value>("another_value");
entt::reflect<int>().data<2048>("max_int");
It goes without saying that accessing them is trivial as well. It's a matter of doing the following, as with any other data member of a meta type:
-
auto value = entt::resolve<my_enum>().data("a_value").get({}).cast<my_enum>();
auto max = entt::resolve<int>().data("max_int").get({}).cast<int>();
As a side note, remember that all this happens behind the scenes without any allocation because of the small object optimization performed by the meta any class.
-
-Properties and meta objects
-
Sometimes (ie when it comes to creating an editor) it might be useful to be able to attach properties to the meta objects created. Fortunately, this is possible for most of them.
+
It goes without saying that accessing them is trivial as well. It's a matter of doing the following, as with any other data member of a meta type:
+
auto value = entt::resolve<my_enum>().data("a_value"_hs).get({}).cast<my_enum>();
auto max = entt::resolve<int>().data("max_int"_hs).get({}).cast<int>();
As a side note, remember that all this happens behind the scenes without any allocation because of the small object optimization performed by the meta any class.
+
+Properties and meta objects
+
Sometimes (for example, when it comes to creating an editor) it might be useful to be able to attach properties to the meta objects created. Fortunately, this is possible for most of them.
To attach a property to a meta object, no matter what as long as it supports properties, it is sufficient to provide an object at the time of construction such that std::get<0> and std::get<1> are valid for it. In other terms, the properties are nothing more than key/value pairs users can put in an std::pair. As an example:
The meta objects that support properties offer then a couple of member functions named prop to iterate them at once and to search a specific property by key:
The meta objects that support properties offer then a couple of member functions named prop to iterate them at once and to search a specific property by key:
// iterate all the properties of a meta type
entt::resolve<my_type>().prop([](auto prop) {
// ...
});
// search for a given property by name
auto prop = entt::resolve<my_type>().prop("tooltip"_hs);
Meta properties are objects having a fairly poor interface, all in all. They only provide the key and the value member functions to be used to retrieve the key and the value contained in the form of meta any objects, respectively.
-
-Unregister types
+
+Unregister types
A type registered with the reflection system can also be unregistered. This means unregistering all its data members, member functions, conversion functions and so on. However, the base classes won't be unregistered, since they don't necessarily depend on it. Similarly, implicitly generated types (as an example, the meta types implicitly generated for function parameters when needed) won't be unregistered.
To unregister a type, users can use the unregister function from the global namespace:
entt::unregister<my_type>();
This function returns a boolean value that is true if the type is actually registered with the reflection system, false otherwise.
diff --git a/autotoc_md69.html b/autotoc_md78.html
similarity index 98%
rename from autotoc_md69.html
rename to autotoc_md78.html
index c6e36b5a8..bb39232ad 100644
--- a/autotoc_md69.html
+++ b/autotoc_md78.html
@@ -22,7 +22,7 @@
EnTT
- 3.0.0
+ 3.1.0
@@ -63,11 +63,11 @@ $(function() {
Crash Course: cooperative scheduler
-
+
Introduction
Sometimes processes are a useful tool to work around the strict definition of a system and introduce logic in a different way, usually without resorting to the introduction of other components.
EnTT offers a minimal support to this paradigm by introducing a few classes that users can use to define and execute cooperative processes.
-
+
The process
A typical process must inherit from the process class template that stays true to the CRTP idiom. Moreover, derived classes must specify what's the intended type for elapsed times.
A process should expose publicly the following member functions whether required (note that it isn't required to define a function unless the derived class wants to override the default behavior):
@@ -90,7 +90,7 @@ The process
Derived classes can also change the internal state of a process by invoking succeed and fail, as well as pause and unpause the process itself. All these are protected member functions made available to be able to manage the life cycle of a process from a derived class.
Here is a minimal example for the sake of curiosity:
Lambdas and functors can't be used directly with a scheduler for they are not properly defined processes with managed life cycles.
This class helps in filling the gap and turning lambdas and functors into full featured processes usable by a scheduler.
@@ -104,7 +104,7 @@ Adaptor
Both succeed and fail accept no parameters at all.
Note that usually users shouldn't worry about creating adaptors at all. A scheduler creates them internally each and every time a lambda or a functor is used as a process.
-
+
The scheduler
A cooperative scheduler runs different processes and helps managing their life cycles.
Each process is invoked once per tick. If it terminates, it's removed automatically from the scheduler and it's never invoked again. Otherwise it's a good candidate to run one more time the next tick.
diff --git a/autotoc_md8.html b/autotoc_md8.html
index 2aca89993..260087bb6 100644
--- a/autotoc_md8.html
+++ b/autotoc_md8.html
@@ -22,7 +22,7 @@
EnTT
- 3.0.0
+ 3.1.0
@@ -71,10 +71,11 @@ Introduction
Design decisions
A bitset-free entity-component system
-
EnTT is a bitset-free entity-component system that doesn't require users to specify the component set at compile-time.
+
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.
This is why users can instantiate the core class simply like:
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.
+
Pay per use
EnTT is entirely designed around the principle that users have to pay only for what they want.
When it comes to using an entity-component system, the tradeoff is usually between performance and memory usage. The faster it is, the more memory it uses. Even worse, some approaches tend to heavily affect other functionalities like the construction and destruction of components to favor iterations, even when it isn't strictly required. In fact, slightly worse performance along non-critical paths are the right price to pay to reduce memory usage and have overall better perfomance sometimes and I've always wondered why this kind of tools do not leave me the choice.
@@ -95,7 +96,7 @@ Stateless systems
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 (actually entt::registry is nothing more than an alias for entt::basic_registry<entt::entity> and entt::entity is an alias for std::uint32_t).
+
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.
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 neither with the registry nor with the entity-component system at all.
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.
The following sections will explain in short how to use the entity-component system, the core part of the whole library.
@@ -103,22 +104,23 @@ Vademecum
The Registry, the Entity and the Component
A registry can store and manage entities, as well as create views and groups to iterate the underlying data structures.
- 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 alias entt::entity for it, as well as 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 information about the entity itself and its version.
-
A registry can be used both to construct and to destroy entities:
-
// constructs a naked entity with no components and returns its identifier
There exists also an overload of the create and destroy member functions that accepts two iterators, that is a range to assign or to destroy. It can be used to create or destroy multiple entities at once:
+ 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>.
+
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 information about the entity itself and its version.
+ User defined identifiers can be introduced by means of the ENTT_OPAQUE_TYPE macro if needed.
+
A registry is used both to construct and to destroy entities:
+
// constructs a naked entity with no components and returns its identifier
auto entity = registry.create();
// destroys an entity and all its components
registry.destroy(entity);
There exists also an overload of the create and destroy member functions that accepts two iterators, that is a range to assign or to destroy. It can be used to create or destroy multiple entities at once:
In both cases, the create member function accepts also a list of default constructible types of components to assign to the entities before to return. It's a faster alternative to the creation and subsequent assignment of components in separate steps.
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.
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:
// returns true if the entity is still valid, false otherwise
bool b = registry.valid(entity);
// gets the version contained in the entity identifier
auto version = registry.version(entity);
// gets the actual version for the given 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.
The assign member function template creates, initializes and assigns to an entity the given component. It accepts a variable number of arguments to construct the component itself if present:
auto &velocity = registry.replace<velocity>(entity);
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):
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:
-
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:
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:
There exist also two other versions of the reset member function:
+
registry.assign<position>(entity, 0., 0.);
// ...
auto &velocity = registry.assign<velocity>(entity);
vel.dx = 0.;
vel.dy = 0.;
If an entity already has the given component, the replace member function template can be used to replace it:
+
registry.replace<position>(entity, 0., 0.);
// ...
auto &velocity = registry.replace<velocity>(entity);
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):
auto &velocity = registry.assign_or_replace<velocity>(entity);
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:
+
if(registry.has<comp>(entity)) {
registry.replace<comp>(entity, arg1, argN);
} else {
registry.assign<comp>(entity, arg1, argN);
}
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:
+
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:
+
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:
+
registry.reset<position>(entity);
There exist also two other versions of the reset member function:
If no entity is passed to it, reset will remove the given component from each entity that has it:
@@ -126,17 +128,17 @@ The Registry, the Entity and the Component
If neither the entity nor the component are specified, all the entities still in use and their components are destroyed:
registry.reset();
Finally, references to components can be retrieved simply by doing this:
auto &[pos, vel] = registry.get<position, velocity>(entity);
The get member function template gives direct access to the component of an entity stored in the underlying data structures of the registry. There exists also an alternative member function named try_get that returns a pointer to the component owned by an entity if any, a null pointer otherwise.
auto &[pos, vel] = registry.get<position, velocity>(entity);
The get member function template gives direct access to the component of an entity stored in the underlying data structures of the registry. There exists also an alternative member function named try_get that returns a pointer to the component owned by an entity if any, a null pointer otherwise.
Observe changes
Because of how the registry works internally, it stores a bunch of signal handlers for each pool in order to notify some of its data structures on the construction and destruction of components or when an instance of a component is explicitly replaced by the user.
These signal handlers are also exposed and made available to users. These are the basic bricks to build fancy things like dependencies and reactive systems.
To get a sink to be used to connect and disconnect listeners so as to be notified on the creation of a component, use the on_construct member function:
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 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.
The function type of a listener for the construction signal should be equivalent to the following:
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.
+
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.
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:
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.
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.
Note also that:
Listeners for the construction signal are invoked after components have been assigned to entities.
@@ -151,14 +153,50 @@ Observe changes
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.
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.
-
+
Please, refer to the documentation of the signal class to know all the features it offers.
+ There are many useful but less known functionalities that aren't described here, such as the connection objects or the possibility to attach listeners with a 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.
+ EnTT tries to take another step in that direction with the observer class template.
+
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:
+
>Imagine you have 100 fighting units on the battlefield but only 10 of them >changed their positions. Instead of using a normal system and updating all 100 >entities depending on the position, you can use a reactive system which will >only update the 10 changed units. So efficient.
+
In EnTT, this means to iterating over a reduced set of entities and components with respect to what would otherwise be returned from a view or a group.
+ On these words, however, the similarities with the proposal of Entitas also 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:
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.
+ 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.
+
However, the most important features of this class are that:
+
+
It's iterable and therefore users can easily walk through the list of entities by means of a range-for loop or the each member function.
+
It's clearable and therefore users can consume the entities and literally reset the observer after each iteration.
+
+
These aspects make the observer an incredibly powerful tool to know at any time what are the entities that matched the given rules since the last time one asked:
+
for(constauto entity: observer) {
// ...
}
observer.clear();
Note that the snippet above is equivalent to the following:
+
observer.each([](constauto entity) {
// ...
});
At least as long as the observer isn't const. This means that the non-const overload of each does also reset the underlying data structure before to return to the caller, while the const overload does not for obvious reasons.
+
The collector is an utility aimed to generate a list of matchers (the actual rules) to use with an observer instead.
+ There are two types of matchers:
+
+
Observing matcher: an observer will return at least all the living entities for which one or more of the given components have been explicitly replaced and not yet destroyed.
Grouping matcher: an observer will return at least all the living entities that would have entered the given group if it existed and that would have not yet left it.
A grouping matcher supports also exclusion lists as well as single components.
+
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.
+ 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.
+
In addition, a matcher can be filtered with a where clause:
This clause introduces a way to intercept entities if and only if they are already part of a hypothetical group. If they are not, they aren't returned by the observer, no matter if they matched the given rule.
+ In the example above, whenever the component sprite of an entity is replaced, the observer probes the entity itself to verify that it has at least position and has not velocity before to store it aside. If one of the two conditions of the filter isn't respected, the entity is discared, no matter what.
+
A where clause accepts a theoretically unlimited number of types as well as multiple elements in the exclusion list. Moreover, every matcher can have it's own clause and multiple clauses for the same matcher are combined in a single one.
+
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.
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.
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.
Once the identifiers are made available, almost everything becomes pretty simple.
+
A journey through a plugin
EnTT comes with an example (actually a test) that shows how to integrate compile-time and runtime components in a stack based JavaScript environment. It uses Duktape under the hood, mainly because I wanted to learn how it works at the time I was writing the code.
The code is not production-ready and overall performance can be highly improved. However, I sacrificed optimizations in favor of a more readable piece of code. I hope I succeeded.
@@ -167,7 +205,7 @@ A journey through a plugin
The basic idea is that of creating a compile-time component aimed to map all the runtime components assigned to an entity.
Identifiers come in use to address the right function from a map when invoked from the runtime environment and to filter entities when iterating.
With a bit of gymnastic, one can narrow views and improve the performance to some extent but it was not the goal of the example.
-
+
Sorting: is it possible?
It goes without saying that sorting entities and components is possible with EnTT.
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.
+
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.
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.
Components can be sorted according to the order imposed by another component:
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.
+
+Helpers
+
The so called helpers are small classes and functions mainly designed to offer built-in support for the most basic functionalities.
+ 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.
+ The library guarantees that the following expression always returns false:
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.
+ 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:
The use of multiple registries is quite common. Examples of use are the separation of the UI from the simulation or the loading of different scenes in the background, possibly on a separate thread, without having to keep track of which entity belongs to which scene.
+ In fact, with EnTT this is even a recommended practice, as the registry is nothing more than a container and different optimizations and strategies can be applied to different containers.
+
Once there are multiple registries available, however, one or more methods are needed to transfer information from one container to another and this results in the stomp member function and a couple of overloads of the create member function for the registry class .
+ The stomp function allows to take one entity from a registry and use it to stomp one or more entities in another registry (or even the same, actually making local copies). On the other hand, the overloads of the create member function can be used to spawn new entities from a prototype.
+
These features open definitely the doors to a lot of interesting features like migrating entities between registries, prototypes, shadow registry, prefabs, shared components without an explicit owner 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.
+ For example, the following adds (or replaces) the component a_type whenever my_type is assigned to an entity:
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.
+
+Tags
+
There's nothing magical about the way tags can be assigned to entities while avoiding a performance hit at runtime. Nonetheless, the syntax can be annoying and that's why a more user-friendly shortcut is provided to do it.
+ This shortcut is the alias template entt::tag.
+
If used in combination with hashed strings, it helps to use tags where types would be required otherwise. As an example:
The actor class is designed for those who don't feel immediately comfortable 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.
+ An actor offers all the functionalities required to work with components, such as the assign andremove member functions, but also has,get, try_get and so on.
+
My advice isn't to use the actor class to hide entities and components behind a more object-oriented interface. Instead, users should rely on it only where strictly necessary. In all other cases, it's highly advisable to become familiar with the model of EnTT and work directly with the registry, the views and the groups, rather than with a tool that could introduce a performance degradation.
+
+Context variables
+
It is often convenient to assign context variables to a registry, so as to make it the only source of truth of an application.
+ 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.
+
Example of use:
+
// creates a new context variable initialized with the given values
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.
+
Snapshot: complete vs continuous
The registry class offers basic support to serialization.
It doesn't convert components to bytes directly, there wasn't the need of another tool for serialization out there. Instead, it accepts an opaque object with a suitable interface (namely an archive) to serialize its internal data structures and restore them later. The way types and instances are converted to a bunch of bytes is completely in charge to the archive and thus to final users.
@@ -202,7 +283,7 @@ Snapshot: complete vs continuous
Note that component stores items along with entities. It means that it works properly without a call to the entities member function.
Once a snapshot is created, there exist mainly two ways to load it: as a whole and in a kind of continuous mode.
The following sections describe both loaders and archives in details.
-
+
Snapshot loader
A snapshot loader requires that the destination registry be empty and loads all the data at once while keeping intact the identifiers that the entities originally had.
To do that, the registry offers a member function named loader that returns a temporary object properly initialized to restore a snapshot.
@@ -211,7 +292,7 @@ Snapshot loader
The entities and destroyed member functions restore the sets of entities and the versions that the entities originally had at the source.
The component member function restores all and only the components specified and assigns them to the right entities. Note that the template parameter list must be exactly the same used during the serialization.
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.
-
+
Continuous loader
A continuous loader is designed to load data from a source registry to a (possibly) non-empty destination. The loader can accommodate in a registry more than one snapshot in a sort of continuous loading that updates the destination one step at a time.
Identifiers that entities originally had are not transferred to the target. Instead, the loader maps remote identifiers to local ones while restoring a snapshot. Because of that, this kind of loader offers a way to update automatically identifiers that are part of components (as an example, as data members or gathered in a container).
@@ -220,73 +301,31 @@ Continuous loader
It isn't necessary to invoke all these functions each and every time. What functions to use in which case mostly depends on the goal and there is not a golden rule to do that. For obvious reasons, what is important is that the data are restored in exactly the same order in which they were serialized.
The entities and destroyed member functions restore groups of entities and map each entity to a local counterpart when required. In other terms, for each remote entity identifier not yet registered by the loader, the latter creates a local identifier so that it can keep the local entity in sync with the remote one.
The component member function restores all and only the components specified and assigns them to the right entities.
- In case the component contains entities itself (either as data members of type entt::entity or as containers of entities), the loader can update them automatically. To do that, it's enough to specify the data members to update as shown in the example.
+ In case the component contains entities itself (either as data members of type entt::entity or as containers of entities), the loader can update them automatically. To do that, it's enough to specify the data members to update as shown in the example.
The orphans member function literally destroys those entities that have no components after a restore. It has exactly the same purpose described in the previous section and works the same way.
Finally, shrink helps to purge local entities that no longer have a remote conterpart. Users should invoke this member function after restoring each snapshot, unless they know exactly what they are doing.
-
+
Archives
Archives must publicly expose a predefined set of member functions. The API is straightforward and consists only of a group of function call operators that are invoked by the snapshot class and the loaders.
In particular:
An output archive, the one used when creating a snapshot, must expose a function call operator with the following signature to store entities:
Where entt::entity is the type of the entities used by the registry. Note that all the member functions of the snapshot class make also an initial call to this endpoint to save the size of the set they are going to store.
+
void operator()(entt::entity);
Where entt::entity is the type of the entities used by the registry. Note that all the member functions of the snapshot class make also an initial call to this endpoint to save the size of the set they are going to store.
In addition, an archive must accept a pair of entity and component for each type to be serialized. Therefore, given a type T, the archive must contain a function call operator with the following signature:
Where entt::entity is the type of the entities used by the registry. Each time the function is invoked, the archive must read the next element from the underlying storage and copy it in the given variable. Note that all the member functions of a loader class make also an initial call to this endpoint to read the size of the set they are going to load.
+
void operator()(entt::entity &);
Where entt::entity is the type of the entities used by the registry. Each time the function is invoked, the archive must read the next element from the underlying storage and copy it in the given variable. Note that all the member functions of a loader class make also an initial call to this endpoint to read the size of the set they are going to load.
In addition, the archive must accept a pair of entity and component for each type to be restored. Therefore, given a type T, the archive must contain a function call operator with the following signature:
Every time such an operator is invoked, the archive must read the next elements from the underlying storage and copy them in the given variables.
-
+
void operator()(entt::entity &, T &);
Every time such an operator is invoked, the archive must read the next elements from the underlying storage and copy them in the given variables.
+
One example to rule them all
EnTT comes with some examples (actually some tests) that show how to integrate a well known library for serialization as an archive. It uses Cereal C++ under the hood, mainly because I wanted to learn how it works at the time I was writing the code.
The code is not production-ready and it isn't neither the only nor (probably) 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.
-
-Prototype
-
A prototype defines a type of an application in terms of its parts. They can be used to assign components to entities of a registry at once.
- Roughly speaking, in most cases prototypes can be considered just as templates to use to initialize entities according to concepts. In fact, users can create how many prototypes they want, each one initialized differently from the others.
-
The following is an example of use of a prototype:
To assign and remove components from a prototype, it offers two dedicated member functions named set and unset. The has member function can be used to know if a given prototype contains one or more components and the get member function can be used to retrieve the components.
-
Creating an entity from a prototype is straightforward:
-
-
To create a new entity from scratch and assign it a prototype, this is the way to go:
Note that existing components aren't overwritten in this case. Only those components that the entity doesn't own yet are copied over. All the other components remain unchanged.
-
Finally, to assign or replace all the components for an entity, thus overwriting existing ones:
In the examples above, the prototype uses its underlying registry to create entities and components both for its purposes and when it's cloned. To use a different repository to clone a prototype, all the member functions accept also a reference to a valid registry as a first argument.
-
Prototypes are a very useful tool that can save a lot of typing sometimes. Furthermore, the codebase may be easier to maintain, since updating a prototype is much less error prone than jumping around in the codebase to update all the snippets copied and pasted around to initialize entities and components.
-
-Helpers
-
The so called helpers are small classes and functions mainly designed to offer built-in support for the most basic functionalities.
- The list of helpers will grow longer as time passes and new ideas come out.
-
-Dependency function
-
A dependency function is a predefined listener, actually a function template to use to automatically assign components to an entity when a type has a dependency on some other types.
- The following adds components a_type and another_type whenever my_type is assigned to an entity:
A component is assigned to an entity and thus default initialized only in case the entity itself hasn't it yet. It means that already existent components won't be overriden.
- A dependency can easily be broken by means of the following function template:
There's nothing magical about the way tags can be assigned to entities while avoiding a performance hit at runtime. Nonetheless, the syntax can be annoying and that's why a more user-friendly shortcut is provided to do it.
- This shortcut is the alias template entt::tag.
-
If used in combination with hashed strings, it helps to use tags where types would be required otherwise. As an example:
In EnTT, there exists a sort of null entity made available to users that is accessible via the entt::null variable.
- The library guarantees that the following expression always returns false:
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.
- 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:
It is often convenient to assign context variables to a registry, so as to make it the only source of truth of an application.
- 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.
-
Example of use:
-
// creates a new context variable initialized with the given values
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.
-
+
Views and Groups
First of all, it is worth answering an obvious question: why views and groups?
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 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.
@@ -298,7 +337,7 @@ Views and Groups
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.
Groups can literally own one or more types of components. 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 only to one group, 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.
-
+
Views
A view behaves differently if it's constructed for a single component or if it has been created to iterate multiple components. Even the API is slightly different in the two cases.
Single component views are specialized in order to give a boost in terms of performance in all the situations. This kind of views can access the underlying data structures directly and avoid superfluous checks. There is nothing as fast as a single component view. In fact, they walk through a packed array of components and return them one at a time.
@@ -311,21 +350,22 @@ Views
Views share the way they are created by means of a registry:
auto &[pos, vel] = view.get<position, velocity>(entity);
// ...
}
Or rely on the each member function to iterate entities and get all their components at once:
-
registry.view<position, velocity>().each([](auto entity, auto &pos, auto &vel) {
// ...
});
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.
+
registry.view<position, velocity>().each([](auto entity, auto &pos, auto &vel) {
// ...
});
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.
+ 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.
As a side note, when using a single component view, the most common error is to invoke get with the type of the component as a template parameter. This is probably due to the fact that it's required for multi component views:
auto view = registry.view<position, const velocity>();
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.
-
+
Runtime views
Runtime 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.
They offer more or less the same functionalities of a multi component view. However, they don't expose a get member function and users should refer to the registry that generated the view to access components. In particular, a runtime 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 runtime view if it contains a given entity.
Refer to the inline documentation for all the details.
Runtime view are extremely cheap to construct and should not be stored around in any case. They should be used immediately after creation and then they should be thrown away. The reasons for this go far beyond the scope of this document.
To iterate a runtime view, either use it in a range-for loop:
-
using component_type = typename decltype(registry)::component_type;
Note: runtime views are meant for all those cases where users don't know at compile-time what components to use to iterate entities. This is particularly well suited to plugin systems and mods in general. Where possible, don't use runtime views, as their performance are slightly inferior to those of the other views.
-
+
Groups
Groups are meant to iterate multiple components at once and offer a faster alternative to views. Roughly speaking, they just play in another league when compared to views.
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, affects functionalities and tries to optimize iterations for all the possible combinations of components. Users can decide when to pay for groups and to what extent.
@@ -337,32 +377,34 @@ Groups
Refer to the inline documentation for all the details.
There is no need to store groups around for they are extremely cheap to construct, even though they can be copied without problems and reused freely. A group performs an initialization step the very first time it's requested and this could be quite costly. To avoid it, consider creating the group when no components have been assigned yet. If the registry is empty, preparation is extremely fast. Groups also return newly created and correctly initialized iterators whenever begin or end are invoked.
To iterate groups, either use them in a range-for loop:
auto &[pos, vel] = group.get<position, velocity>(entity);
// ...
}
Or rely on the each member function to iterate entities and get all their components at once:
-
registry.group<position>(entt::get<velocity>).each([](auto entity, auto &pos, auto &vel) {
// ...
});
The each member function is highly optimized. Unless users want to iterate 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.
auto &[pos, vel] = group.get<position, velocity>(entity);
// ...
}
Or rely on the each member function to iterate entities and get all their components at once:
+
registry.group<position>(entt::get<velocity>).each([](auto entity, auto &pos, auto &vel) {
// ...
});
The each member function is highly optimized. Unless users want to iterate 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.
-
+
Full-owning groups
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.
A full-owning group is created as:
-
auto group = registry.group<position, velocity>();
Filtering entities by components is also supported:
-
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.
-
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 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 underlying data structure).
-
+
auto group = registry.group<position, velocity>();
Filtering entities by components is also supported:
+
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.
+
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).
+ The elements that aren't part of the group can still be sorted separately for each pool using the sort member function of the registry.
+
Partial-owning groups
A partial-owning group works similarly to a full-owning group for the components it owns, but relies on indirection to get components owned by other groups. This isn't as fast as a full-owning group, but it's already much faster than views when there are only one or two free components to retrieve (the most common cases likely). In the worst case, it's not slower than views anyway.
A partial-owning group is created as:
-
auto group = registry.group<position>(entt::get<velocity>);
Filtering entities by components is also supported:
-
auto group = registry.group<position>(entt::get<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 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 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 underlying data structure).
-
+
auto group = registry.group<position>(entt::get<velocity>);
Filtering entities by components is also supported:
+
auto group = registry.group<position>(entt::get<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 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).
+ Regarding the owned types, the elements that aren't part of the group can still be sorted separately for each pool using the sort member function of the registry.
+
Non-owning groups
Non-owning groups are usually fast enough, for sure faster than views and well suited for most of the cases. However, they require custom data structures to work properly and they increase memory consumption. As a rule of thumb, users should avoid using non-owning groups, if possible.
A non-owning group is created as:
-
auto group = registry.group<>(entt::get<position, velocity>);
Filtering entities by components is also supported:
-
auto group = registry.group<>(entt::get<position, velocity>, entt::exclude<renderable>);
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.
+
auto group = registry.group<>(entt::get<position, velocity>);
Filtering entities by components is also supported:
+
auto group = registry.group<>(entt::get<position, velocity>, entt::exclude<renderable>);
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.
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).
-
+
Types: const, non-const and all in between
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.
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:
@@ -371,9 +413,9 @@ Types: const, non-const and all in between
In other terms, these statements are all valid:
position &pos = view.get<position>(entity);
const position &cpos = view.get<const position>(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:
-
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.
+
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.
The same concepts apply to groups as well.
-
+
Give me everything
Views and groups are narrow windows on the entire list of entities. They work by filtering entities according to their components.
In some cases there may be the need to iterate all the entities still in use regardless of their components. The registry offers a specific member function to do that:
To test the orphanity of a single entity, use the member function orphan instead. It accepts a valid entity identifer as an argument and returns true in case the entity is an orphan, false otherwise.
In general, all these functions can result in poor performance. each is fairly slow because of some checks it performs on each and every entity. For similar reasons, orphans can be even slower. Both functions should not be used frequently to avoid the risk of a performance hit.
-
+
What is allowed and what is not
Most of the ECS available out there don't allow to create and destroy entities and components during iterations. EnTT partially solves the problem with a few limitations:
@@ -395,7 +437,7 @@ What is allowed and what is not
In these cases, iterators aren't invalidated. To be clear, it doesn't mean that also references will continue to be valid.
Consider the following example:
-
registry.view<position>([&](constauto entity, auto &pos) {
The each member function won't break (because iterators aren't invalidated) but there are no guarantees on references. Use a common range-for loop and get components directly from the view or move the creation of components at the end of the function to avoid dangling pointers.
+
registry.view<position>([&](constauto entity, auto &pos) {
The each member function won't break (because iterators aren't invalidated) but there are no guarantees on references. Use a common range-for loop and get components directly from the view or move the creation of components at the end of the function to avoid dangling pointers.
Iterators are invalidated instead and the behavior is undefined if an entity is modified or destroyed and it's not the one currently returned by the iterator nor a newly created one.
To work around it, possible approaches are:
@@ -403,7 +445,7 @@ What is allowed and what is not
Mark entities and components with a proper tag component that indicates they must be purged, then perform a second iteration to clean them up one by one.
A notable side effect of this feature is that the number of required allocations is further reduced in most of the cases.
-
+
More performance, more constraints
Groups are a (much) faster alternative to views. However, the higher the performance, the greater the constraints on what is allowed and what is not.
In particular, groups add in some rare cases a limitation on the creation of components during iterations. It happens in quite particular cases. Given the nature and the scope of the groups, it isn't something in which it will happen to come across probably, but it's good to know it anyway.
@@ -416,7 +458,7 @@ More performance, more constraints
In other words, the limitation doesn't exist as long as a type is treated as a free type (as an example with multi component views and partial- or non-owning groups) or iterated with its own group, but it can occur if the type is used as a main type to rule on an iteration.
This happens because groups own the pools of their components and organize the data internally to maximize performance. Because of that, full consistency for owned components is guaranteed only when they are iterated as part of their groups or as free types with multi component views and groups in general.
-
+
Empty type optimization
An empty type T is such that std::is_empty_v<T> returns true. They are also the same types for which empty base optimization (EBO) is possibile. EnTT handles these types in a special way, optimizing both in terms of performance and memory usage. However, this also has consequences that are worth mentioning.
@@ -424,7 +466,7 @@ Empty type optimization
On the other hand, iterations are faster because only the entities to which the type is assigned are considered. Moreover, less memory is used, since there doesn't exist any instance of the component, no matter how many entities it is assigned to.
For similar reasons, wherever a function type of a listener accepts a component, it cannot be caught by a non-const reference. Capture it by copy or by const reference instead.
More in general, none of the features offered by the library is affected, but for the ones that require to return actual instances.
-
+
Multithreading
In general, the entire registry isn't thread safe as it is. Thread safety isn't something that users should want out of the box for several reasons. Just to mention one of them: performance.
Views, groups and consequently the approach adopted by EnTT are the great exception to the rule. It's true that views, groups and iterators in general aren't thread safe by themselves. Because of this users shouldn't try to iterate a set of components and modify the same set concurrently. However:
@@ -434,14 +476,19 @@ Multithreading
This kind of entity-component systems can be used in single threaded applications as well as along with async stuff or multiple threads. Moreover, typical thread based models for ECS don't require a fully thread safe registry to work. Actually, users can reach the goal with the registry as it is while working with most of the common models.
Because of the few reasons mentioned above and many others not mentioned, users are completely responsible for synchronization whether required. On the other hand, they could get away with it without having to resort to particular expedients.
-
+
Iterators
A special mention is needed for the iterators returned by the views and the groups. Most of the time they meet the requirements of random access iterators, in all cases they meet at least the requirements of forward iterators.
In other terms, they are suitable for use with the parallel algorithms of the standard library. If it's not clear, this is a great thing.
As an example, this kind of iterators can be used in combination with std::for_each and std::execution::par to parallelize the visit and therefore the update of the components returned by a view or a group, as long as the constraints previously discussed are respected:
auto view = registry.view<position, const velocity>();
This can increase the throughput considerably, even without resorting to who knows what artifacts that are difficult to maintain over time.
Unfortunately, because of the limitations of the current revision of the standard, the parallel std::for_each accepts only forward iterators. This means that the iterators provided by the library cannot return proxy objects as references and must return actual reference types instead.
- This may change in the future and the iterators will almost certainly return both the entities and a list of references to their components sooner or later. Multi-pass guarantee won't break in any case and the performance should even benefit from it further.
+ This may change in the future and the iterators will almost certainly return both the entities and a list of references to their components sooner or later. Multi-pass guarantee won't break in any case and the performance should even benefit from it further.
+
+Beyond this document
+
There are many other features and functions not listed in this document.
+ EnTT and in particular its ECS part is in continuous development and some things could be forgotten, others could have been omitted on purpose to reduce the size of this file. Unfortunately, some parts may even be outdated and still to be updated.
+
For further information, it's recommended to refer to the documentation included in the code itself or join the official channels to ask a question.
diff --git a/autotoc_md74.html b/autotoc_md83.html
similarity index 93%
rename from autotoc_md74.html
rename to autotoc_md83.html
index 538044a1b..240c0ef6c 100644
--- a/autotoc_md74.html
+++ b/autotoc_md83.html
@@ -22,7 +22,7 @@
EnTT
- 3.0.0
+ 3.1.0
@@ -63,13 +63,13 @@ $(function() {
Crash Course: resource management
-
+
Introduction
Resource management is usually one of the most critical part of a software like a game. Solutions are often tuned to the particular application. There exist several approaches and all of them are perfectly fine as long as they fit the requirements of the piece of software in which they are used.
Examples are loading everything on start, loading on request, predictive loading, and so on.
EnTT doesn't pretend to offer a one-fits-all solution for the different cases. Instead, it offers a minimal and perhaps trivial cache that can be useful most of the time during prototyping and sometimes even in a production environment.
For those interested in the subject, the plan is to improve it considerably over time in terms of performance, memory usage and functionalities. Hoping to make it, of course, one step at a time.
-
+
The resource, the loader and the cache
There are three main actors in the model: the resource, the loader and the cache.
The resource is whatever users want it to be. An image, a video, an audio, whatever. There are no limits.
@@ -86,8 +86,8 @@ The resource, the loader and the cache
A cache offers a set of basic functionalities to query its internal state and to organize it:
// gets the number of resources managed by a cache
// checks if a cache contains at least a valid resource
constauto empty = cache.empty();
// clears a cache and discards its content
cache.clear();
Besides these member functions, a cache contains what is needed to load, use and discard resources of the given type.
Before to explore this part of the interface, it makes sense to mention how resources are identified. The type of the identifiers to use is defined as:
Where resource_type is an alias for entt::hashed_string::hash_type. Therefore, resource identifiers are created explicitly as in the following example:
Where resource_type is an alias for entt::hashed_string::hash_type. Therefore, resource identifiers are created explicitly as in the following example:
The class hashed_string is described in a dedicated section, so I won't go in details here.
Resources are loaded and thus stored in a cache through the load member function. It accepts the loader to use as a template parameter, the resource identifier and the parameters used to construct the resource as arguments:
The function returns a handle to the resource, whether it already exists or is loaded. In case the loader returns an invalid pointer, the handle is invalid as well and therefore it can be easily used with an if statement:
Before trying to load a resource, the contains member function can be used to know if a cache already contains a specific resource:
diff --git a/autotoc_md77.html b/autotoc_md86.html
similarity index 58%
rename from autotoc_md77.html
rename to autotoc_md86.html
index 262e53acf..c68a8aa7f 100644
--- a/autotoc_md77.html
+++ b/autotoc_md86.html
@@ -22,7 +22,7 @@
EnTT
- 3.0.0
+ 3.1.0
@@ -63,58 +63,62 @@ $(function() {
Crash Course: events, signals and everything in between
-
+
Introduction
Signals are usually a core part of games and software architectures in general.
Roughly speaking, they help to decouple the various parts of a system while allowing them to communicate with each other somehow.
The so called _modern C++_ comes with a tool that can be useful in these terms, the std::function. As an example, it can be used to create delegates.
However, there is no guarantee that an std::function does not perform allocations under the hood and this could be problematic sometimes. Furthermore, it solves a problem but may not adapt well to other requirements that may arise from time to time.
-
In case that the flexibility and potential of an std::function are not required or where you are looking for something different, EnTT offers a full set of classes to solve completely different problems.
-
+
In case that the flexibility and power of an std::function isn't required or if the price to pay for them is too high,EnTT offers a complete set of lightweight classes to solve the same and many other problems.
+
Delegate
A delegate can be used as a general purpose invoker with no memory overhead for free functions and members provided along with an instance on which to invoke them.
- It does not claim to be a drop-in replacement for an std::function, so do not expect to use it whenever an std::function fits well. However, it can be used to send opaque delegates around to be used to invoke functions as needed.
+ It does not claim to be a drop-in replacement for an std::function, so do not expect to use it whenever an std::function fits well. That said, it's most likely even a better fit than an std::function in a lot of cases, so expect to use it quite a lot anyway.
The interface is trivial. It offers a default constructor to create empty delegates:
All what is needed to create an instance is to specify the type of the function the delegate will contain, that is the signature of the free function or the member function one wants to assign to it.
All what is needed to create an instance is to specify the type of the function the delegate will contain, that is the signature of the free function or the member one wants to assign to it.
Attempting to use an empty delegate by invoking its function call operator results in undefined behavior or most likely a crash. Before to use a delegate, it must be initialized.
There exists a bunch of overloads of the connect member function to do that. As an example of use:
The delegate class accepts also data members, if needed. In this case, the function type of the delegate is such that the parameter list is empty and the value of the data member is at least convertible to the return type.
- Functions having type equivalent to void(T *, args...) are accepted as well. In this case, T * is considered a payload and the function will receive it back every time it's invoked. In other terms, this works just fine with the above definition:
The function g will be invoked with a pointer to c and 42. However, the function type of the delegate is still void(int), mainly because this is also the signature of its function call operator.
-
To create and initialize a delegate at once, there are also some specialized constructors. Because of the rules of the language, the listener is provided by means of the entt::connect_arg variable template:
The delegate class accepts also data members, if needed. In this case, the function type of the delegate is such that the parameter list is empty and the value of the data member is at least convertible to the return type.
+
Free functions having type equivalent to void(T &, args...) are accepted as well. In this case, T & is considered a payload and the function will receive it back every time it's invoked. In other terms, this works just fine with the above definition:
The function g will be invoked with a reference to c and 42. However, the function type of the delegate is still void(int). This is also the signature of its function call operator.
+
Another interesting aspect of the delegate class is that it accepts also functions with a list of parameters that is shorter than that of the function type used to specialize the delegate itself.
+ The following code is therefore perfectly valid:
Where the function type of the delegate is void(int) as above. It goes without saying that the extra arguments are silently discarded internally.
+v This is a nice-to-have feature in a lot of cases, as an example when the delegate class is used as a building block of a signal-slot system.
+
To create and initialize a delegate at once, there are a few specialized constructors. Because of the rules of the language, the listener is provided by means of the entt::connect_arg variable template:
Aside connect, a disconnect counterpart isn't provided. Instead, there exists a reset member function to use to clear a delegate.
To know if a delegate is empty, it can be used explicitly in every conditional statement:
-
if(delegate) {
// ...
}
Finally, to invoke a delegate, the function call operator is the way to go as usual:
As shown above, listeners do not have to strictly follow the signature of the delegate. As long as a listener can be invoked with the given arguments to yield a result that is convertible to the given result type, everything works just fine.
-
Probably too much small and pretty poor of functionalities, but the delegate class can help in a lot of cases and it has shown that it is worth keeping it within the library.
-
+
if(delegate) {
// ...
}
Finally, to invoke a delegate, the function call operator is the way to go as already shown in the examples above:
In all cases, the listeners don't have to strictly follow the signature of the delegate. As long as a listener can be invoked with the given arguments to yield a result that is convertible to the given result type, everything works just fine.
+
Signals
-
Signal handlers work with naked pointers, function pointers and pointers to member functions. Listeners can be any kind of objects and users are in charge of connecting and disconnecting them from a signal to avoid crashes due to different lifetimes. On the other side, performance shouldn't be affected that much by the presence of such a signal handler.
- A signal handler can be used as a private data member without exposing any publish functionality to the clients of a class. The basic idea is to impose a clear separation between the signal itself and its sink class, that is a tool to be used to connect and disconnect listeners on the fly.
-
The API of a signal handler is straightforward. The most important thing is that it comes in two forms: with and without a collector. In case a signal is associated with a collector, all the values returned by the listeners can be literally collected and used later by the caller. Otherwise it works just like a plain signal that emits events from time to time.
-
-
Note: collectors are allowed only in case of function types whose the return type isn't void for obvious reasons.
-
To create instances of signal handlers there exist mainly two ways:
As expected, they offer all the basic functionalities required to know how many listeners they contain (size) or if they contain at least a listener (empty) and even to swap two signal handlers (swap).
+
Signal handlers work with references to classes, function pointers and pointers to members. Listeners can be any kind of objects and users are in charge of connecting and disconnecting them from a signal to avoid crashes due to different lifetimes. On the other side, performance shouldn't be affected that much by the presence of such a signal handler.
+ Signals make use of delegates internally and therefore they undergo the same rules and offer similar functionalities. It may be a good idea to consult the documentation of the delegate class for further information.
+
A signal handler can be used as a private data member without exposing any publish functionality to the clients of a class. The basic idea is to impose a clear separation between the signal itself and the sink class, that is a tool to be used to connect and disconnect listeners on the fly.
+
The API of a signal handler is straightforward. The most important thing is that it comes in two forms: with and without a collector. In case a signal is provided with a collector, all the values returned by the listeners can be literally collected and used later by the caller. Otherwise it works just like a plain signal that emits events from time to time.
+ To create instances of signal handlers it is sufficient to provide the type of function to which they refer:
Signals offer all the basic functionalities required to know how many listeners they contain (size) or if they contain at least a listener (empty), as well as a function to use to swap handlers (swap).
Besides them, there are member functions to use both to connect and disconnect listeners in all their forms by means of a sink:
As shown above, listeners do not have to strictly follow the signature of the signal. As long as a listener can be invoked with the given arguments to yield a result that is convertible to the given result type, everything works just fine.
As shown above, the listeners don't have to strictly follow the signature of the signal. As long as a listener can be invoked with the given arguments to yield a result that is convertible to the given return type, everything works just fine.
+ The connect member function returns by default a connection object to be used as an alternative to break a connection by means of its release member function. A scoped_connection can also be created from a connection. In this case, the link is broken automatically as soon as the object goes out of scope.
Once listeners are attached (or even if there are no listeners at all), events and data in general can be published through a signal by means of the publish member function:
signal.publish(42, 'c');
To collect data, the collect member function should be used instead. Below is a minimal example to show how to use it:
A collector must expose a function operator that accepts as an argument a type to which the return type of the listeners can be converted. Moreover, it has to return a boolean value that is false to stop collecting data, true otherwise. This way one can avoid calling all the listeners in case it isn't necessary.
A collector must expose a function operator that accepts as an argument a type to which the return type of the listeners can be converted. Moreover, it can optionally return a boolean value that is true to stop collecting data, false otherwise. This way one can avoid calling all the listeners in case it isn't necessary.
+ Functors can also be used in place of a lambda. Since the collector is copied when invoking the collect member function, std::ref is the way to go in this case:
The event dispatcher class is designed so as to be used in a loop. It allows users both to trigger immediate events or to queue events to be published all together once per tick.
This class shares part of its API with the one of the signal handler, but it doesn't require that all the types of events are specified when declared:
-
// define a general purpose dispatcher that works with naked pointers
In order to register an instance of a class to a dispatcher, its type must expose one or more member functions the arguments of which are such that const E & can be converted to them for each type of event E, no matter what the return value is.
+
In order to register an instance of a class to a dispatcher, its type must expose one or more member functions the arguments of which are such that const E & can be converted to them for each type of event E, no matter what the return value is.
The name of the member function aimed to receive the event must be provided to the connect member function of the sink in charge for the specific event:
The trigger member function serves the purpose of sending an immediate event to all the listeners registered so far. It offers a convenient approach that relieves users from having to create the event itself. Instead, it's enough to specify the type of event and provide all the parameters required to construct it.
+
The trigger member function serves the purpose of sending an immediate event to all the listeners registered so far. It offers a convenient approach that relieves users from having to create the event itself. Instead, it's enough to specify the type of event and provide all the parameters required to construct it.
As an example:
dispatcher.trigger<an_event>(42);
dispatcher.trigger<another_event>();
Listeners are invoked immediately, order of execution isn't guaranteed. This method can be used to push around urgent messages like an is terminating notification on a mobile app.
On the other hand, the enqueue member function queues messages together and allows to maintain control over the moment they are sent to listeners. The signature of this method is more or less the same of trigger:
dispatcher.enqueue<an_event>(42);
dispatcher.enqueue<another_event>();
Events are stored aside until the update member function is invoked, then all the messages that are still pending are sent to the listeners at once:
// emits all the events of the given type at once
dispatcher.update<my_event>();
// emits all the events queued so far at once
dispatcher.update();
This way users can embed the dispatcher in a loop and literally dispatch events once per tick to their systems.
-
+
Event emitter
A general purpose event emitter thought mainly for those cases where it comes to working with asynchronous stuff.
Originally designed to fit the requirements of uvw (a wrapper for libuv written in modern C++), it was adapted later to be included in this library.
A non-owning group returns all the entities and only the entities that have at least the given components. Moreover, it's guaranteed that the entity list is tightly packed in memory for fast iterations.
+ In general, non-owning groups don't stay true to the order of any set of components unless users explicitly sort them.
+
Important
+
Iterators aren't invalidated if:
+
+
New instances of the given components are created and assigned to entities.
+
The entity currently pointed is modified (as an example, if one of the given components is removed from the entity to which the iterator points).
+
The entity currently pointed is destroyed.
+
+
In all the other cases, modifying the pools of the given components in any way invalidates all the iterators and using them results in undefined behavior.
+
Note
Groups share references to the underlying data structures of the registry that generated them. Therefore any change to the entities and to the components made by means of the registry are immediately reflected by all the groups.
+ Moreover, 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).
+
Warning
Lifetime of a group must overcome the one of the registry that generated it. In any other case, attempting to use a group results in undefined behavior.
+
Template Parameters
+
+
Entity
A valid entity type (see entt_traits for more details).
Direct access to the list of entities of a given pool.
+
The returned pointer is such that range [data<Component>(), data<Component>() + size<Component>()] is always a valid range, even if the container is empty.
+
Note
There are no guarantees on the order of the entities. Use begin and end if you want to iterate the group in the expected order.
Iterates entities and components and applies the given function object to them.
+
The function object is invoked for each entity. It is provided with the entity itself and a set of references to all its components. The constness of the components is as requested.
+ The signature of the function must be equivalent to one of the following forms:
Empty types aren't explicitly instantiated. Therefore, temporary objects are returned during iterations. They can be caught only by copy or with const references.
Returns an iterator that is past the last entity that has the given components.
+
The returned iterator points to the entity following the last entity that has the given components. Attempting to dereference the returned iterator results in undefined behavior.
+
Note
Input iterators stay true to the order imposed to the underlying data structures.
+
Returns
An iterator to the entity following the last entity that has the given components.
Returns the components assigned to the given entity.
+
Prefer this function instead of registry::get during iterations. It has far better performance than its companion function.
+
Warning
Attempting to use an invalid component type results in a compilation error. Attempting to use an entity that doesn't belong to the group results in undefined behavior.
+ An assertion will abort the execution at runtime in debug mode if the group doesn't contain the given entity.
Iterates entities and components and applies the given function object to them.
+
The function object is invoked for each entity. It is provided with the entity itself and a set of references to non-empty components. The constness of the components is as requested.
+ The signature of the function must be equivalent to one of the following forms:
Direct access to the list of components of a given pool.
+
The returned pointer is such that range [raw<Component>(), raw<Component>() + size<Component>()] is always a valid range, even if the container is empty.
+
Note
There are no guarantees on the order of the components. Use begin and end if you want to iterate the group in the expected order.
Sort a group according to the given comparison function.
+
Sort the group so that iterating it with a couple of iterators returns entities and components in the expected order. See begin and end for more details.
+
The comparison function object must return true if the first element is less than the second one, false otherwise. The signature of the comparison function should be equivalent to one of the following:
Where Component are such that they are iterated by the group.
+ Moreover, the comparison function object shall induce a strict weak ordering on the values.
+
The sort function oject must offer a member function template operator() that accepts three arguments:
+
+
An iterator to the first element of the range to sort.
+
An iterator past the last element of the range to sort.
+
A comparison function to use to compare the elements.
+
+
The comparison function object received by the sort function object hasn't necessarily the type of the one passed along with the other parameters to this member function.
+
Note
Attempting to iterate elements using a raw pointer returned by a call to either data or raw gives no guarantees on the order, even though sort has been invoked.
+
Template Parameters
+
+
Component
Optional types of components to compare.
+
Compare
Type of comparison function object.
+
Sort
Type of sort function object.
+
Args
Types of arguments to forward to the sort function object.
+
+
+
+
Parameters
+
+
compare
A valid comparison function object.
+
algo
A valid sort function object.
+
args
Arguments to forward to the sort function object, if any.
Sort the shared pool of entities according to the given component.
+
Non-owning groups of the same type share with the registry a pool of entities with its own order that doesn't depend on the order of any pool of components. Users can order the underlying data structure so that it respects the order of the pool of the given component.
+
Note
The shared pool of entities and thus its order is affected by the changes to each and every pool that it tracks. Therefore changes to those pools can quickly ruin the order imposed to the pool of entities shared between the non-owning groups.
New instances of the given components are created and assigned to entities.
The entity currently pointed is modified (as an example, if one of the given components is removed from the entity to which the iterator points).
+
The entity currently pointed is destroyed.
In all the other cases, modifying the pools of the given components in any way invalidates all the iterators and using them results in undefined behavior.
Note
Groups share references to the underlying data structures of the registry that generated them. Therefore any change to the entities and to the components made by means of the registry are immediately reflected by all the groups. Moreover, sorting an 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 underlying data structure).
Direct access to the list of entities of a given pool.
The returned pointer is such that range [data<Component>(), data<Component>() + size<Component>()] is always a valid range, even if the container is empty.
- Moreover, in case the group owns the given component, the range [data<Component>(), data<Component>() + size()] is such that it contains the entities that are part of the group itself.
+ Moreover, in case the group owns the given component, the range [data<Component>(), data<Component>() + size()] is such that it contains the entities that are part of the group itself.
Note
There are no guarantees on the order of the entities. Use begin and end if you want to iterate the group in the expected order.
Iterates entities and components and applies the given function object to them.
The function object is invoked for each entity. It is provided with the entity itself and a set of references to all its components. The constness of the components is as requested.
The signature of the function must be equivalent to one of the following forms:
Empty types aren't explicitly instantiated. Therefore, temporary objects are returned during iterations. They can be caught only by copy or with const references.
+
void(constentity_type, Owned &, Other &..., Get &...);
void(Owned &, Other &..., Get &...);
Note
Empty types aren't explicitly instantiated. Therefore, temporary objects are returned during iterations. They can be caught only by copy or with const references.
Returns the components assigned to the given entity.
-
Prefer this function instead of registry::get during iterations. It has far better performance than its companion function.
+
Prefer this function instead of registry::get during iterations. It has far better performance than its companion function.
Warning
Attempting to use an invalid component type results in a compilation error. Attempting to use an entity that doesn't belong to the group results in undefined behavior.
An assertion will abort the execution at runtime in debug mode if the group doesn't contain the given entity.
Iterates entities and components and applies the given function object to them.
+
The function object is invoked for each entity. It is provided with the entity itself and a set of references to non-empty components. The constness of the components is as requested.
+ The signature of the function must be equivalent to one of the following forms:
Direct access to the list of components of a given pool.
The returned pointer is such that range [raw<Component>(), raw<Component>() + size<Component>()] is always a valid range, even if the container is empty.
- Moreover, in case the group owns the given component, the range [raw<Component>(), raw<Component>() + size()] is such that it contains the instances that are part of the group itself.
+ Moreover, in case the group owns the given component, the range [raw<Component>(), raw<Component>() + size()] is such that it contains the instances that are part of the group itself.
Note
There are no guarantees on the order of the components. Use begin and end if you want to iterate the group in the expected order.
Sort a group according to the given comparison function.
Sort the group so that iterating it with a couple of iterators returns entities and components in the expected order. See begin and end for more details.
The comparison function object must return true if the first element is less than the second one, false otherwise. The signature of the comparison function should be equivalent to one of the following:
-
bool(const Component &..., const Component &...);
bool(const Entity, const Entity);
Where Component are either owned types or not but still such that they are iterated by the group.
+
Where Component are either owned types or not but still such that they are iterated by the group.
Moreover, the comparison function object shall induce a strict weak ordering on the values.
The sort function oject must offer a member function template operator() that accepts three arguments:
A non-owning group returns all the entities and only the entities that have at least the given components. Moreover, it's guaranteed that the entity list is tightly packed in memory for fast iterations.
- In general, non-owning groups don't stay true to the order of any set of components unless users explicitly sort them.
-
Important
-
Iterators aren't invalidated if:
-
-
New instances of the given components are created and assigned to entities.
-
The entity currently pointed is modified (as an example, if one of the given components is removed from the entity to which the iterator points).
-
-
In all the other cases, modifying the pools of the given components in any way invalidates all the iterators and using them results in undefined behavior.
-
Note
Groups share references to the underlying data structures of the registry that generated them. Therefore any change to the entities and to the components made by means of the registry are immediately reflected by all the groups.
- Moreover, 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).
-
Warning
Lifetime of a group must overcome the one of the registry that generated it. In any other case, attempting to use a group results in undefined behavior.
-
Template Parameters
-
-
Entity
A valid entity type (see entt_traits for more details).
Direct access to the list of entities of a given pool.
-
The returned pointer is such that range [data<Component>(), data<Component>() + size<Component>()] is always a valid range, even if the container is empty.
-
Note
There are no guarantees on the order of the entities. Use begin and end if you want to iterate the group in the expected order.
Iterates entities and components and applies the given function object to them.
-
The function object is invoked for each entity. It is provided with the entity itself and a set of references to all its components. The constness of the components is as requested.
- The signature of the function must be equivalent to one of the following forms:
Empty types aren't explicitly instantiated. Therefore, temporary objects are returned during iterations. They can be caught only by copy or with const references.
Returns an iterator that is past the last entity that has the given components.
-
The returned iterator points to the entity following the last entity that has the given components. Attempting to dereference the returned iterator results in undefined behavior.
-
Note
Input iterators stay true to the order imposed to the underlying data structures.
-
Returns
An iterator to the entity following the last entity that has the given components.
Returns the components assigned to the given entity.
-
Prefer this function instead of registry::get during iterations. It has far better performance than its companion function.
-
Warning
Attempting to use an invalid component type results in a compilation error. Attempting to use an entity that doesn't belong to the group results in undefined behavior.
- An assertion will abort the execution at runtime in debug mode if the group doesn't contain the given entity.
Direct access to the list of components of a given pool.
-
The returned pointer is such that range [raw<Component>(), raw<Component>() + size<Component>()] is always a valid range, even if the container is empty.
-
Note
There are no guarantees on the order of the components. Use begin and end if you want to iterate the group in the expected order.
Sort the shared pool of entities according to the given component.
-
Non-owning groups of the same type share with the registry a pool of entities with its own order that doesn't depend on the order of any pool of components. Users can order the underlying data structure so that it respects the order of the pool of the given component.
-
Note
The shared pool of entities and thus its order is affected by the changes to each and every pool that it tracks. Therefore changes to those pools can quickly ruin the order imposed to the pool of entities shared between the non-owning groups.
Returns directly the numeric representation of a string view. More...
+
Detailed Description
-
Zero overhead unique identifier.
-
TURN_OFF_DOXYGEN A hashed string is a compile-time tool that allows users to use human-readable identifers in the codebase while using their numeric counterparts at runtime.
- Because of that, a hashed string can also be used in constant expressions if required.
TURN_OFF_DOXYGEN A hashed string is a compile-time tool that allows users to use human-readable identifers in the codebase while using their numeric counterparts at runtime.
+ Because of that, a hashed string can also be used in constant expressions if required.
Constructs a hashed string from an array of const chars.
+
Constructs a hashed string from an array of const characters.
Forcing template resolution avoids implicit conversions. An human-readable identifier can be anything but a plain, old bunch of characters.
- Example of use:
Returns directly the numeric representation of a string.
Forcing template resolution avoids implicit conversions. An human-readable identifier can be anything but a plain, old bunch of characters.
- Example of use:
An observer returns all the entities and only the entities that fit the requirements of at least one matcher. Moreover, it's guaranteed that the entity list is tightly packed in memory for fast iterations.
+ In general, observers don't stay true to the order of any set of components.
+
Observers work mainly with two types of matchers, provided through a collector:
+
+
Observing matcher: an observer will return at least all the living entities for which one or more of the given components have been explicitly replaced and not yet destroyed.
+
Grouping matcher: an observer will return at least all the living entities that would have entered the given group if it existed and that would have not yet left it.
+
+
If an entity respects the requirements of multiple matchers, it will be returned once and only once by the observer in any case.
+
Matchers support also filtering by means of a where clause that accepts both a list of types and an exclusion list.
+ Whenever a matcher finds that an entity matches its requirements, the condition of the filter is verified before to register the entity itself. Moreover, a registered entity isn't returned by the observer if the condition set by the filter is broken in the meantime.
+
Important
+
Iterators aren't invalidated if:
+
+
New instances of the given components are created and assigned to entities.
+
The entity currently pointed is modified (as an example, if one of the given components is removed from the entity to which the iterator points).
+
The entity currently pointed is destroyed.
+
+
In all the other cases, modifying the pools of the given components in any way invalidates all the iterators and using them results in undefined behavior.
+
Warning
Lifetime of an observer doesn't necessarily have to overcome the one of the registry to which it is connected. However, the observer must be disconnected from the registry before being destroyed to avoid crashes due to dangling pointers.
+
Template Parameters
+
+
Entity
A valid entity type (see entt_traits for more details).
Returns an iterator that is past the last entity of the observer.
+
The returned iterator points to the entity following the last entity of the observer. Attempting to dereference the returned iterator results in undefined behavior.
+
Returns
An iterator to the entity following the last entity of the observer.
A prototype is used to define a concept in terms of components.
- Prototypes act as templates for those specific types of an application which users would otherwise define through a series of component assignments to entities. In other words, prototypes can be used to assign components to entities of a registry at once.
-
Note
Components used along with prototypes must be copy constructible. Prototypes wrap component types with custom types, so they do not interfere with other users of the registry they were built with.
-
Warning
Prototypes directly use their underlying registries to store entities and components for their purposes. Users must ensure that the lifetime of a registry and its contents exceed that of the prototypes that use it.
-
Template Parameters
-
-
Entity
A valid entity type (see entt_traits for more details).
After prototype move construction, instances that have been moved from are placed in a valid but unspecified state. It's highly discouraged to continue using them.
Assigns the components of a prototype to a given entity.
-
Assigning a prototype to an entity won't overwrite existing components under any circumstances.
- In other words, only those components that the entity doesn't own yet are copied over. All the other components remain unchanged.
-
Note
The registry may or may not be different from the one already used by the prototype. There is also an overload that directly uses the underlying registry.
-
Warning
Attempting to use an invalid entity results in undefined behavior.
- An assertion will abort the execution at runtime in debug mode in case of invalid entity.
Assigns the components of a prototype to a given entity.
-
Assigning a prototype to an entity won't overwrite existing components under any circumstances.
- In other words, only those components that the entity doesn't own yet are copied over. All the other components remain unchanged.
-
Note
This overload directly uses the underlying registry as a working space. Therefore, the components of the prototype and of the entity will share the same registry.
-
Warning
Attempting to use an invalid entity results in undefined behavior.
- An assertion will abort the execution at runtime in debug mode in case of invalid entity.
Assigns or replaces the components of a prototype for an entity.
-
Existing components are overwritten, if any. All the other components will be copied over to the target entity.
-
Note
The registry may or may not be different from the one already used by the prototype. There is also an overload that directly uses the underlying registry.
-
Warning
Attempting to use an invalid entity results in undefined behavior.
- An assertion will abort the execution at runtime in debug mode in case of invalid entity.
Assigns or replaces the components of a prototype for an entity.
-
Existing components are overwritten, if any. All the other components will be copied over to the target entity.
-
Note
This overload directly uses the underlying registry as a working space. Therefore, the components of the prototype and of the entity will share the same registry.
-
Warning
Attempting to use an invalid entity results in undefined behavior.
- An assertion will abort the execution at runtime in debug mode in case of invalid entity.
The registry may or may not be different from the one already used by the prototype. There is also an overload that directly uses the underlying registry.
This overload directly uses the underlying registry as a working space. Therefore, the components of the prototype and of the entity will share the same registry.
Attempting to get a component from a prototype that doesn't own it results in undefined behavior.
- An assertion will abort the execution at runtime in debug mode if the prototype doesn't own an instance of the given component.
-
Template Parameters
-
-
Component
Types of components to get.
-
-
-
-
Returns
References to the components owned by the prototype.
Attempting to get a component from a prototype that doesn't own it results in undefined behavior.
- An assertion will abort the execution at runtime in debug mode if the prototype doesn't own an instance of the given component.
-
Template Parameters
-
-
Component
Types of components to get.
-
-
-
-
Returns
References to the components owned by the prototype.
Assigns the components of a prototype to an entity.
-
Assigning a prototype to an entity won't overwrite existing components under any circumstances.
- In other words, only the components that the entity doesn't own yet are copied over. All the other components remain unchanged.
-
Note
The registry may or may not be different from the one already used by the prototype. There is also an overload that directly uses the underlying registry.
-
Warning
Attempting to use an invalid entity results in undefined behavior.
- An assertion will abort the execution at runtime in debug mode in case of invalid entity.
Assigns the components of a prototype to an entity.
-
Assigning a prototype to an entity won't overwrite existing components under any circumstances.
- In other words, only the components that the entity doesn't own yet are copied over. All the other components remain unchanged.
-
Note
This overload directly uses the underlying registry as a working space. Therefore, the components of the prototype and of the entity will share the same registry.
-
Warning
Attempting to use an invalid entity results in undefined behavior.
- An assertion will abort the execution at runtime in debug mode in case of invalid entity.
The registry may or may not be different from the one already used by the prototype. There is also an overload that directly uses the underlying registry.
This overload directly uses the underlying registry as a working space. Therefore, the components of the prototype and of the entity will share the same registry.
After prototype move assignment, instances that have been moved from are placed in a valid but unspecified state. It's highly discouraged to continue using them.
Clones the given components and all the entity identifiers.
-
The components must be copiable for obvious reasons. The entities maintain their versions once copied.
- If no components are provided, the registry will try to clone all the existing pools.
+
Returns a full or partial copy of a registry.
+
The components must be copyable for obvious reasons. The entities maintain their versions once copied.
+ If no components are provided, the registry will try to clone all the existing pools. The ones for non-copyable types won't be cloned.
+
This feature supports exclusion lists. The excluded types have higher priority than those indicated for cloning. An excluded type will never be cloned.
Note
There isn't an efficient way to know if all the entities are assigned at least one component once copied. Therefore, there may be orphans. It is up to the caller to clean up the registry if necessary.
Listeners and groups aren't copied. It is up to the caller to connect the listeners of interest to the new registry and to set up groups.
@@ -624,17 +642,18 @@ Listeners and groups aren't copied. It is up to the caller to connect the listen
Newly created ones in case no entities have been previously destroyed.
Recycled ones with updated versions.
-
Users should not care about the type of the returned entity identifier. In case entity identifers are stored around, the valid member function can be used to know if they are still valid or the entity has been destroyed and potentially recycled.
-
The returned entity has assigned the given components, if any. The components must be at least default constructible. A compilation error will occur otherwhise.
+
Users should not care about the type of the returned entity identifier. In case entity identifers are stored around, the valid member function can be used to know if they are still valid or the entity has been destroyed and potentially recycled.
+ The returned entity has assigned the given components, if any.
+
The components must be at least default constructible. A compilation error will occur otherwhise.
A valid entity identifier if the component list is empty, a tuple containing the entity identifier and the references to the components just created otherwise.
The components must be at least move and default insertable. A compilation error will occur otherwhise.
Template Parameters
Component
Types of components to assign to the entity.
@@ -734,9 +755,154 @@ template<typename... Component, typename It >
-
Returns
No return value if the component list is empty, a tuple containing the pointers to the arrays of components just created and sorted the same of the entities otherwise.
+
Returns
No return value if the component list is empty, a tuple containing the iterators to the lists of components just created and sorted the same of the entities otherwise.
Returns a reference to the given component for an entity.
In case the entity doesn't own the component, the parameters provided are used to construct it.
Equivalent to the following snippet (pseudocode):
-
auto &component = registry.has<Component>(entity) ? registry.get<Component>(entity) : registry.assign<Component>(entity, args...);
Prefer this function anyway because it has slightly better performance.
+
auto &component = registry.has<Component>(entity) ? registry.get<Component>(entity) : registry.assign<Component>(entity, args...);
Prefer this function anyway because it has slightly better performance.
Warning
Attempting to use an invalid entity results in undefined behavior.
An assertion will abort the execution at runtime in debug mode in case of invalid entity.
This kind of objects are created on the fly and share with the registry its internal data structures.
- Feel free to discard a group after the use. Creating and destroying a group is an incredibly cheap operation because they do not require any type of initialization, but for the first time they are requested.
- As a rule of thumb, storing a group should never be an option.
-
Groups support exclusion lists and can own types of components. The more types are owned by a group, the faster it is to iterate entities and components.
- However, groups also affect some features of the registry such as the creation and destruction of components, which will consequently be slightly slower (nothing that can be noticed in most cases).
-
Note
Pools of components that are owned by a group cannot be sorted anymore. The group takes the ownership of the pools and arrange components so as to iterate them as fast as possible.
This kind of objects are created on the fly and share with the registry its internal data structures.
- Feel free to discard a group after the use. Creating and destroying a group is an incredibly cheap operation because they do not require any type of initialization, but for the first time they are requested.
- As a rule of thumb, storing a group should never be an option.
-
Groups support exclusion lists and can own types of components. The more types are owned by a group, the faster it is to iterate entities and components.
- However, groups also affect some features of the registry such as the creation and destruction of components, which will consequently be slightly slower (nothing that can be noticed in most cases).
-
Note
Pools of components that are owned by a group cannot be sorted anymore. The group takes the ownership of the pools and arrange components so as to iterate them as fast as possible.
This kind of objects are created on the fly and share with the registry its internal data structures.
- Feel free to discard a group after the use. Creating and destroying a group is an incredibly cheap operation because they do not require any type of initialization, but for the first time they are requested.
- As a rule of thumb, storing a group should never be an option.
-
Groups support exclusion lists and can own types of components. The more types are owned by a group, the faster it is to iterate entities and components.
- However, groups also affect some features of the registry such as the creation and destruction of components, which will consequently be slightly slower (nothing that can be noticed in most cases).
-
Note
Pools of components that are owned by a group cannot be sorted anymore. The group takes the ownership of the pools and arrange components so as to iterate them as fast as possible.
The loader returned by this function requires that the registry be empty. In case it isn't, all the data will be automatically deleted before to return.
A sink is an opaque object used to connect listeners to components.
The sink returned by this function can be used to receive notifications whenever a new instance of the given component is created and assigned to an entity.
The function type for a listener is equivalent to:
-
void(registry<Entity> &, Entity, Component &);
Listeners are invoked after the component has been assigned to the entity. The order of invocation of the listeners isn't guaranteed.
+
void(Entity, registry<Entity> &, Component &);
Listeners are invoked after the component has been assigned to the entity. The order of invocation of the listeners isn't guaranteed.
Note
Empty types aren't explicitly instantiated. Therefore, temporary objects are returned through signals. They can be caught only by copy or with const references.
A sink is an opaque object used to connect listeners to components.
The sink returned by this function can be used to receive notifications whenever an instance of the given component is removed from an entity and thus destroyed.
The function type for a listener is equivalent to:
-
void(registry<Entity> &, Entity);
Listeners are invoked before the component has been removed from the entity. The order of invocation of the listeners isn't guaranteed.
+
void(Entity, registry<Entity> &);
Listeners are invoked before the component has been removed from the entity. The order of invocation of the listeners isn't guaranteed.
Note
Empty types aren't explicitly instantiated. Therefore, temporary objects are returned through signals. They can be caught only by copy or with const references.
A sink is an opaque object used to connect listeners to components.
The sink returned by this function can be used to receive notifications whenever an instance of the given component is explicitly replaced.
The function type for a listener is equivalent to:
-
void(registry<Entity> &, Entity, Component &);
Listeners are invoked before the component has been replaced. The order of invocation of the listeners isn't guaranteed.
+
void(Entity, registry<Entity> &, Component &);
Listeners are invoked before the component has been replaced. The order of invocation of the listeners isn't guaranteed.
Note
Empty types aren't explicitly instantiated. Therefore, temporary objects are returned through signals. They can be caught only by copy or with const references.
Destroys all the entities. After a call to reset, all the entities still in use are recycled with a new version number. In case entity identifers are stored around, the valid member function can be used to know if they are still valid.
@@ -2582,7 +2781,7 @@ template<typename Entity>
It can be used to save and restore its internal state or to keep two or more instances of this class in sync, as an example in a client-server architecture.
A comparison function to use to compare the elements.
The comparison funtion object received by the sort function object hasn't necessarily the type of the one passed along with the other parameters to this member function.
-
Warning
Pools of components that are owned by a group cannot be sorted.
- An assertion will abort the execution at runtime in debug mode in case the pool is owned by a group.
+
Warning
Pools of components owned by a group are only partially sorted.
+ In other words, only the elements that aren't part of the group are sorted by this function. Use the sort member function of a group to sort the other half of the pool.
@@ -2703,7 +2902,7 @@ template<typename To , typename From >
All the entities in A that are not in B are returned in no particular order after all the other entities.
Any subsequent change to B won't affect the order in A.
-
Warning
Pools of components that are owned by a group cannot be sorted.
+
Warning
Pools of components owned by a group cannot be sorted this way.
An assertion will abort the execution at runtime in debug mode in case the pool is owned by a group.
Template Parameters
@@ -2713,7 +2912,165 @@ template<typename To , typename From >
-
The components must be copyable for obvious reasons. The entities must be both valid.
+ If no components are provided, the registry will try to copy all the existing types. The non-copyable ones will be ignored.
+
This feature supports exclusion lists as an alternative to component lists. An excluded type will never be copied.
+
Warning
Attempting to copy components that aren't copyable results in unexpected behaviors.
+ A static assertion will abort the compilation when the components provided aren't copy constructible. Otherwise, an assertion will abort the execution at runtime in debug mode in case one or more types cannot be copied.
+
+Attempting to use invalid entities results in undefined behavior.
+ An assertion will abort the execution at runtime in debug mode in case of invalid entities.
The given component doesn't need to be necessarily in use.
Do not use this functionality to generate numeric identifiers for types at runtime. They aren't guaranteed to be stable between different runs.
@@ -135,6 +135,7 @@ class entt::basic_runtime_view< Entity >
New instances of the given components are created and assigned to entities.
The entity currently pointed is modified (as an example, if one of the given components is removed from the entity to which the iterator points).
+
The entity currently pointed is destroyed.
In all the other cases, modifying the pools of the given components in any way invalidates all the iterators and using them results in undefined behavior.
Note
Views share references to the underlying data structures of the registry that generated them. Therefore any change to the entities and to the components made by means of the registry are immediately reflected by the views, unless a pool was missing when the view was built (in this case, the view won't have a valid reference and won't be updated accordingly).
@@ -146,7 +147,7 @@ class entt::basic_runtime_view< Entity >
-
This class is a refinement of a sparse set that associates an object to an entity. The main purpose of this class is to extend sparse sets to store components in a registry. It guarantees fast access both to the elements and to the entities.
-
Note
Entities and objects have the same order. It's guaranteed both in case of raw access (either to entities or objects) and when using input iterators.
+
Note
Entities and objects have the same order. It's guaranteed both in case of raw access (either to entities or objects) and when using random or input access iterators.
Internal data structures arrange elements to maximize performance. Because of that, there are no guarantees that elements have the expected order when iterate directly the internal packed array (see raw and size member functions for that). Use begin and end instead.
Warning
Empty types aren't explicitly instantiated. Temporary objects are returned in place of the instances of the components and raw access isn't available for them.
@@ -280,10 +302,10 @@ Internal data structures arrange elements to maximize performance. Because of th
Assigns one or more entities to a storage and constructs their objects.
-
The object type must be at least default constructible.
+
Assigns one or more entities to a storage and default constructs their objects.
+
The object type must be at least move and default insertable.
Warning
Attempting to assign an entity that already belongs to the storage results in undefined behavior.
An assertion will abort the execution at runtime in debug mode if the storage already contains the given entity.
Template Parameters
@@ -337,9 +359,76 @@ template<typename It >
-
Returns
A pointer to the array of instances just created and sorted the same of the entities.
+
Returns
An iterator to the list of instances just created and sorted the same of the entities.
The returned iterator points to the element following the last instance of the given type. Attempting to dereference the returned iterator results in undefined behavior.
-
Note
Input iterators stay true to the order imposed by a call to either sort or respect.
+
Note
Random access iterators stay true to the order imposed by a call to either sort or respect.
Returns
An iterator to the element following the last instance of the given type.
The returned iterator points to the element following the last instance of the given type. Attempting to dereference the returned iterator results in undefined behavior.
-
Note
Input iterators stay true to the order imposed by a call to either sort or respect.
+
Note
Random access iterators stay true to the order imposed by a call to either sort or respect.
Returns
An iterator to the element following the last instance of the given type.
The returned iterator points to the element following the last instance of the given type. Attempting to dereference the returned iterator results in undefined behavior.
-
Note
Input iterators stay true to the order imposed by a call to either sort or respect.
+
Note
Random access iterators stay true to the order imposed by a call to either sort or respect.
Returns
An iterator to the element following the last instance of the given type.
There are no guarantees on the order, even though either sort or respect has been previously invoked. Internal data structures arrange elements to maximize performance. Accessing them directly gives a performance boost but less guarantees. Use begin and end if you want to iterate the storage in the expected order.
There are no guarantees on the order, even though either sort or respect has been previously invoked. Internal data structures arrange elements to maximize performance. Accessing them directly gives a performance boost but less guarantees. Use begin and end if you want to iterate the storage in the expected order.
Sort instances according to the order of the entities in another sparse set.
-
Entities that are part of both the storage are ordered internally according to the order they have in other. All the other entities goes to the end of the list and there are no guarantess on their order. Instances are sorted according to the entities to which they belong.
- In other terms, this function can be used to impose the same order on two sets by using one of them as a master and the other one as a slave.
-
Iterating the storage with a couple of iterators returns elements in the expected order after a call to respect. See begin and end for more details.
-
Note
Attempting to iterate elements using a raw pointer returned by a call to either data or raw gives no guarantees on the order, even though respect has been invoked.
-
Parameters
-
-
other
The sparse sets that imposes the order of the entities.
Sort instances according to the given comparison function.
-
Sort the elements so that iterating the storage with a couple of iterators returns them in the expected order. See begin and end for more details.
+
Sort elements according to the given comparison function.
+
Sort the elements so that iterating the range with a couple of iterators returns them in the expected order. See begin and end for more details.
The comparison function object must return true if the first element is less than the second one, false otherwise. The signature of the comparison function should be equivalent to one of the following:
bool(const Entity, const Entity);
bool(const Type &, const Type &);
Moreover, the comparison function object shall induce a strict weak ordering on the values.
The sort function oject must offer a member function template operator() that accepts three arguments:
Swaps entities and objects in the internal packed arrays.
+
Warning
Attempting to swap entities that don't belong to the sparse set results in undefined behavior.
+ An assertion will abort the execution at runtime in debug mode if the sparse set doesn't contain the given entities.
This class is a refinement of a sparse set that associates an object to an entity. The main purpose of this class is to extend sparse sets to store components in a registry. It guarantees fast access both to the elements and to the entities.
-
Note
Entities and objects have the same order. It's guaranteed both in case of raw access (either to entities or objects) and when using input iterators.
+
Note
Entities and objects have the same order. It's guaranteed both in case of raw access (either to entities or objects) and when using random or input access iterators.
Internal data structures arrange elements to maximize performance. Because of that, there are no guarantees that elements have the expected order when iterate directly the internal packed array (see raw and size member functions for that). Use begin and end instead.
Warning
Empty types aren't explicitly instantiated. Temporary objects are returned in place of the instances of the components and raw access isn't available for them.
@@ -242,8 +250,75 @@ Internal data structures arrange elements to maximize performance. Because of th
The object type must be at least default constructible.
+
Warning
Attempting to assign an entity that already belongs to the storage results in undefined behavior.
+ An assertion will abort the execution at runtime in debug mode if the storage already contains the given entity.
+
Template Parameters
+
+
It
Type of forward iterator.
+
+
+
+
Parameters
+
+
first
An iterator to the first element of the range of entities.
+
last
An iterator past the last element of the range of entities.
+
+
+
+
Returns
An iterator to the list of instances just created and sorted the same of the entities.