From bf10cbc70bb47a5277c41f7b352eb5dd9183e17a Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Sat, 1 Sep 2018 14:57:06 +0200 Subject: [PATCH] review: documentation --- CMakeLists.txt | 2 - README.md | 2498 +------------------------------------------ docs/CMakeLists.txt | 14 + docs/core.md | 167 +++ docs/entity.md | 1400 ++++++++++++++++++++++++ docs/locator.md | 75 ++ docs/process.md | 211 ++++ docs/resource.md | 240 +++++ docs/shared.md | 39 + docs/signal.md | 412 +++++++ 10 files changed, 2576 insertions(+), 2482 deletions(-) create mode 100644 docs/core.md create mode 100644 docs/entity.md create mode 100644 docs/locator.md create mode 100644 docs/process.md create mode 100644 docs/resource.md create mode 100644 docs/shared.md create mode 100644 docs/signal.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 600d61fbc..d32b3ce32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -218,6 +218,4 @@ add_custom_target( README.md TODO .travis.yml - docs/CONTRIBUTING.md - docs/extra.dox ) diff --git a/README.md b/README.md index d1a121130..9e5c8959f 100644 --- a/README.md +++ b/README.md @@ -12,61 +12,21 @@ # Table of Contents * [Introduction](#introduction) + * [Code Example](#code-example) + * [Motivation](#motivation) + * [Performance](#performance) * [Build Instructions](#build-instructions) -* [Crash Course: entity-component system](#crash-course-entity-component-system) - * [Design choices](#design-choices) - * [A bitset-free entity-component system](#a-bitset-free-entity-component-system) - * [Pay per use](#pay-per-use) - * [Vademecum](#vademecum) - * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component) - * [Single instance components](#single-instance-components) - * [Observe changes](#observe-changes) - * [Who let the tags out?](#who-let-the-tags-out) - * [Runtime components](#runtime-components) - * [A journey through a plugin](#a-journey-through-a-plugin) - * [Sorting: is it possible?](#sorting-is-it-possible) - * [Snapshot: complete vs continuous](#snapshot-complete-vs-continuous) - * [Snapshot loader](#snapshot-loader) - * [Continuous loader](#continuous-loader) - * [Archives](#archives) - * [One example to rule them all](#one-example-to-rule-them-all) - * [Prototype](#prototype) - * [Helpers](#helpers) - * [Dependency function](#dependency-function) - * [Labels](#labels) - * [Null entity](#null-entity) - * [View: to persist or not to persist?](#view-to-persist-or-not-to-persist) - * [Standard View](#standard-view) - * [Single component standard view](#single-component-standard-view) - * [Multi component standard view](#multi-component-standard-view) - * [Persistent View](#persistent-view) - * [Raw View](#raw-view) - * [Runtime View](#runtime-view) - * [Give me everything](#give-me-everything) - * [Iterations: what is allowed and what is not](#iterations-what-is-allowed-and-what-is-not) - * [Multithreading](#multithreading) -* [Crash Course: core functionalities](#crash-course-core-functionalities) - * [Compile-time identifiers](#compile-time-identifiers) - * [Runtime identifiers](#runtime-identifiers) - * [Hashed strings](#hashed-strings) - * [Monostate](#monostate) -* [Crash Course: service locator](#crash-course-service-locator) -* [Crash Course: cooperative scheduler](#crash-course-cooperative-scheduler) - * [The process](#the-process) - * [The scheduler](#the-scheduler) -* [Crash Course: resource management](#crash-course-resource-management) - * [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache) -* [Crash Course: events, signals and everything in between](#crash-course-events-signals-and-everything-in-between) - * [Signals](#signals) - * [Delegate](#delegate) - * [Event dispatcher](#event-dispatcher) - * [Event emitter](#event-emitter) + * [Requirements](#requirements) + * [Library](#library) + * [Documentation](#documentation) + * [Tests](#tests) * [Packaging Tools](#packaging-tools) * [EnTT in Action](#entt-in-action) +* [Contributors](#contributors) * [License](#license) * [Support](#support) - * [Donation](#donation) - * [Hire me](#hire-me) + * [Donation](#donation) + * [Hire me](#hire-me) @@ -74,7 +34,8 @@ # Introduction `EnTT` is a header-only, tiny and easy to use entity-component system (and much -more) written in modern C++.
+more) written in modern C++ and even +[used by Mojang in Minecraft](https://minecraft.net/en-us/attribution/).
The entity-component-system (also known as _ECS_) is an architectural pattern used mostly in game development. For further details: @@ -94,7 +55,9 @@ Here is a brief, yet incomplete list of what it offers today: * An incredibly fast entity-component system based on sparse sets, with its own views and a _pay for what you use_ policy to adjust performance and memory usage according to users' requirements. -* Actor class for those who aren't confident with entity-component systems. +* A lot of facilities built on top of the entity-component system to help + developers and avoid reinventing the wheel (ie dependencies, snapshot, actor + class for those who aren't confident with the architecture and so on). * The smallest and most basic implementation of a service locator ever seen. * A cooperative scheduler for processes of any type. * All what is needed for resource management (cache, loaders, handles). @@ -270,46 +233,6 @@ Use the line below to include only the entity-component system instead: Then pass the proper `-I` argument to the compiler to add the `src` directory to the include paths. -### Side note: shared libraries - -To make sure that an application and a shared library that use both `EnTT` can -interact correctly when symbols are hidden by default, there are some tricks to -follow.
-In particular and in order to avoid undefined behaviors, all the instantiation -of the `Family` class template shall be made explicit along with the system-wide -specifier to use to export them. - -At the time I'm writing this document, the classes that use internally the above -mentioned class template are `Dispatcher`, `Emitter` and `Registry`. Therefore -and as an example, if you use the `Registry` class template in your shared -library and want to set symbols visibility to _hidden_ by default, the following -lines are required to allow it to function properly with a client that also uses -the `Registry` somehow: - -* On GNU/Linux: - - ```cpp - namespace entt { - template class __attribute__((visibility("default"))) Family; - template class __attribute__((visibility("default"))) Family; - template class __attribute__((visibility("default"))) Family; - } - ``` - -* On Windows: - - ```cpp - namespace entt { - template class __declspec(dllexport) Family; - template class __declspec(dllexport) Family; - template class __declspec(dllexport) Family; - } - ``` - -Otherwise, the risk is that type identifiers are different between the shared -library and the application and this will prevent the whole thing from -functioning correctly for obvious reasons. - ## Documentation The documentation is based on [doxygen](http://www.stack.nl/~dimitri/doxygen/). @@ -329,7 +252,9 @@ The API reference will be created in HTML format within the directory @cond TURN_OFF_DOXYGEN --> The API reference is also available [online](https://skypjack.github.io/entt/) -for the latest version. +for the latest version.
+There exists also a [wiki](https://github.com/skypjack/entt/wiki) dedicated to +the project where users can find all related documentation pages. @@ -349,2393 +274,6 @@ To build the most basic set of tests: Note that benchmarks are not part of this set. -# Crash Course: entity-component system - -## Design choices - -### 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.
-This is why users can instantiate the core class simply like: - -```cpp -entt::DefaultRegistry registry; -``` - -In place of its more annoying and error-prone counterpart: - -```cpp -entt::DefaultRegistry registry; -``` - -### 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. -However, slightly worse performance along non-critical paths are the right price -to pay to reduce memory usage and I've always wondered why this kind of tools do -not leave me the choice.
-`EnTT` follows a completely different approach. It squeezes the best from the -basic data structures and gives users the possibility to pay more for higher -performance where needed.
-The disadvantage of this approach is that users need to know the systems they -are working on and the tools they are using. Otherwise, the risk to ruin the -performance along critical paths is high. - -So far, this choice has proven to be a good one and I really hope it can be for -many others besides me. - -## Vademecum - -The `Registry` to store, the views 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 the `DefaultRegistry` is nothing more than a -`Registry` where the type of the entities is `std::uint32_t`).
-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` or a view 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.
-In fact, the project is composed of many other classes in addition to those -describe below. For more details, please refer to the inline documentation. - -## The Registry, the Entity and the Component - -A registry can store and manage entities, as well as create views to iterate the -underlying data structures.
-`Registry` is a class template that 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 an alias named `DefaultRegistry` for -`Registry`. - -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 destroy entities: - -```cpp -// 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); -``` - -Entities can also be destroyed _by type_, that is by specifying the types of the -tags or components that identify them: - -```cpp -// destroys the entity that owns the given tag, if any -registry.destroy(entt::tag_t{}); - -// destroys the entities that own the given components, if any -registry.destroy(); -``` - -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 get out of the them all the -information they carry: - -```cpp -// 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: - -```cpp -registry.assign(entity, 0., 0.); - -// ... - -Velocity &velocity = registry.assign(entity); -velocity.dx = 0.; -velocity.dy = 0.; -``` - -If an entity already has the given component, the `replace` member function -template can be used to replace it: - -```cpp -registry.replace(entity, 0., 0.); - -// ... - -Velocity &velocity = registry.replace(entity); -velocity.dx = 0.; -velocity.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, `accommodate` 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): - -```cpp -registry.accommodate(entity, 0., 0.); - -// ... - -Velocity &velocity = registry.accommodate(entity); -velocity.dx = 0.; -velocity.dy = 0.; -``` - -Note that `accommodate` is a slightly faster alternative for the following -`if/else` statement and nothing more: - -```cpp -if(registry.has(entity)) { - registry.replace(entity, arg1, argN); -} else { - registry.assign(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: - -```cpp -bool b = registry.has(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: - -```cpp -registry.remove(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: - -```cpp -registry.reset(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: - - ```cpp - registry.reset(); - ``` - -* If neither the entity nor the component are specified, all the entities still - in use and their components are destroyed: - - ```cpp - registry.reset(); - ``` - -Finally, references to components can be retrieved simply by doing this: - -```cpp -const auto &cregistry = registry; - -// const and non-const reference -const Position &position = cregistry.get(entity); -Position &position = registry.get(entity); - -// const and non-const references -std::tuple tup = cregistry.get(entity); -std::tuple tup = registry.get(entity); -``` - -The `get` member function template gives direct access to the component of an -entity stored in the underlying data structures of the registry. - -### Single instance components - -In those cases where all what is needed is a single instance component, tags are -the right tool to achieve the purpose.
-Tags undergo the same requirements of components. They can be either plain old -data structures or more complex and movable data structures with a proper -constructor.
-Actually, the same type can be used both as a tag and as a component and the -registry will not complain about it. It is up to users to properly manage their -own types. In some cases, the tag `tag_t` must also be used in order to -disambiguate overloads of member functions. - -Attaching tags to entities and removing them is trivial: - -```cpp -auto player = registry.create(); -auto camera = registry.create(); - -// attaches a default-initialized tag to an entity -registry.assign(entt::tag_t{}, player); - -// attaches a tag to an entity and initializes it -registry.assign(entt::tag_t{}, camera, player); - -// removes tags from their owners -registry.remove(); -registry.remove(); -``` - -In case a tag already has an owner, its content can be updated by means of the -`replace` member function template and the ownership of the tag can be -transferred to another entity using the `move` member function template: - -``` -// replaces the content of the given tag -Point &point = registry.replace(entt::tag_t{}, 1.f, 1.f); - -// transfers the ownership of the tag to another entity -entity_type prev = registry.move(next); -``` - -If in doubt about whether or not a tag already has an owner, the `has` member -function template may be useful: - -```cpp -bool b = registry.has(); -``` - -References to tags can be retrieved simply by doing this: - -```cpp -const auto &cregistry = registry; - -// either a non-const reference ... -PlayingCharacter &player = registry.get(); - -// ... or a const one -const Camera &camera = cregistry.get(); -``` - -The `get` member function template gives direct access to the tag as stored in -the underlying data structures of the registry. - -As shown above, in almost all the cases the entity identifier isn't required. -Since a single instance component can have only one associated entity, it -doesn't make much sense to mention it explicitly.
-To find out who the owner is, just do the following: - -```cpp -auto player = registry.attachee(); -``` - -Note that iterating tags isn't possible for obvious reasons. Tags give direct -access to single entities and nothing more. - -### Observe changes - -Because of how the registry works internally, it stores a couple of signal -handlers for each pool in order to notify some of its data structures on the -construction and destruction of components.
-These signal handlers are also exposed and made available to users. This is the -basic brick 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 `construction` member function: - -```cpp -// connects a free function -registry.construction().connect<&MyFreeFunction>(); - -// connects a member function -registry.construction().connect(&instance); - -// disconnects a free function -registry.construction().disconnect<&MyFreeFunction>(); - -// disconnects a member function -registry.construction().disconnect(&instance); -``` - -To be notified when components are destroyed, use the `destruction` member -function instead. - -The function type of a listener is the same in both cases: - -```cpp -void(Registry &, Entity); -``` - -In other terms, a listener is provided with the registry that triggered the -notification and the entity affected by the change. Note also that: - -* Listeners are invoked **after** components have been assigned to entities. -* Listeners are invoked **before** components have been removed from entities. -* The order of invocation of the listeners isn't guaranteed in any case. - -There are also some limitations on what a listener can and cannot do. In -particular: - -* Connecting and disconnecting other functions from within the body of a - listener should be avoided. It can lead to undefined behavior in some cases. -* Assigning and removing components and tags from within the body of a listener - that observes the destruction of instances of a given type should be avoided. - It can lead to undefined behavior in some cases. This type of listeners is - intended to provide users with an easy way to perform cleanup and nothing - more. - -To a certain extent, these limitations do not apply. However, it is 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. - -#### Who let the tags out? - -As an extension, signals are also provided with tags. Although they are not -strictly required internally, it makes sense that a user expects signal support -even when it comes to tags actually.
-Signals for tags undergo exactly the same requirements of those introduced for -components. Also the function type for a listener is the same and it's invoked -with the same guarantees discussed above. - -To get the sinks for a tag just use tag `tag_t` to disambiguate overloads of -member functions as in the following example: - -```cpp -registry.construction(entt::tag_t{}).connect<&MyFreeFunction>(); -registry.destruction(entt::tag_t{}).connect(&instance); -``` - -Listeners for tags and components are managed separately and do not influence -each other in any case. Therefore, note that the greater the number of listeners -for a type, the greater the performance hit when a tag of the given type is -created or destroyed. - -### 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. - -In `EnTT`, identifiers are easily accessible: - -```cpp -entt::DefaultRegistry registry; - -// standard component identifier -auto ctype = registry.type(); - -// single instance component identifier -auto ttype = registry.type(entt::tag_t{}); -``` - -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`](https://github.com/svaarala/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.
-Note also that this isn't neither the only nor (probably) the best way to do it. -In fact, the right way depends on the scripting language and the problem one is -facing in general.
-That being said, feel free to use it at your own risk. - -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: - -* Components can be sorted directly: - - ```cpp - registry.sort([](const auto &lhs, const auto &rhs) { - return lhs.z < rhs.z; - - }); - ``` - - 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 pattern of usage 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: - - ```cpp - registry.sort(); - ``` - - In this case, instances of `Movement` are arranged in memory so that cache - misses are minimized when the two components are iterated together. - -### Snapshot: complete vs continuous - -The `Registry` class offers basic support to serialization.
-It doesn't convert components and tags 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. - -The goal of the serialization part is to allow users to make both a dump of the -entire registry or a narrower snapshot, that is to select only the components -and the tags in which they are interested.
-Intuitively, the use cases are different. As an example, the first approach is -suitable for local save/restore functionalities while the latter is suitable for -creating client-server applications and for transferring somehow parts of the -representation side to side. - -To take a snapshot of the registry, use the `snapshot` member function. It -returns a temporary object properly initialized to _save_ the whole registry or -parts of it. - -Example of use: - -```cpp -OutputArchive output; - -registry.snapshot() - .entities(output) - .destroyed(output) - .component(output) - .tag(output); -``` - -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. - -The `entities` member function asks the registry to serialize all the entities -that are still in use along with their versions. On the other side, the -`destroyed` member function tells to the registry to serialize the entities that -have been destroyed and are no longer in use.
-These two functions can be used to save and restore the whole set of entities -with the versions they had during serialization. - -The `component` member function is a function template the aim of which is to -store aside components. The presence of a template parameter list is a -consequence of a couple of design choices from the past and in the present: - -* First of all, there is no reason to force a user to serialize all the - components at once and most of the times it isn't desiderable. As an example, - in case the stuff for the HUD in a game is put into the registry for some - reasons, its components can be freely discarded during a serialization step - because probably the software already knows how to reconstruct the HUD - correctly from scratch. - -* Furthermore, the registry makes heavy use of _type-erasure_ techniques - internally and doesn't know at any time what types of components it contains. - Therefore being explicit at the call point is mandatory. - -There exists also another version of the `component` member function that -accepts a range of entities to serialize. This version is a bit slower than the -other one, mainly because it iterates the range of entities more than once for -internal purposes. However, it can be used to filter out those entities that -shouldn't be serialized for some reasons.
-As an example: - -```cpp -const auto view = registry.view(); -OutputArchive output; - -registry.snapshot() - .component(output, view.cbegin(), view.cend()); -``` - -The `tag` member function is similar to the previous one, apart from the fact -that it works with tags and not with components.
-Note also that both `component` and `tag` store items along with entities. It -means that they work 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 `restore` that returns a -temporary object properly initialized to _restore_ a snapshot. - -Example of use: - -```cpp -InputArchive input; - -registry.restore() - .entities(input) - .destroyed(input) - .component(input) - .tag(input) - .orphans(); -``` - -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 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 same applies to the -`tag` member function. - -The `orphans` member function literally destroys those entities that have -neither components nor tags. 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 and tags are saved, it could happen that some of the entities have -neither components nor tags 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 or tags (as an example, as -data members or gathered in a container).
-Another difference with the snapshot loader is that the continuous loader does -not need to work with the private data structures of a registry. Furthermore, it -has an internal state that must persist over time. Therefore, there is no reason -to create it by means of a registry, or to limit its lifetime to that of a -temporary object. - -Example of use: - -```cpp -entt::ContinuousLoader loader{registry}; -InputArchive input; - -loader.entities(input) - .destroyed(input) - .component(input, &DirtyComponent::parent, &DirtyComponent::child) - .tag(input, &DirtyTag::container) - .orphans() - .shrink(); -``` - -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` and `tag` member functions restore all and only the components -and the tags specified and assign them to the right entities.
-In case the component or the tag contains entities itself (either as data -members of type `entity_type` 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 -neither components nor tags 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: - - ```cpp - void operator()(Entity); - ``` - - Where `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 either component or - tag for each type to be serialized. Therefore, given a type `T`, the archive - must contain a function call operator with the following signature: - - ```cpp - void operator()(Entity, const T &); - ``` - - The output archive can freely decide how to serialize the data. The register - is not affected at all by the decision. - -* An input archive, the one used when restoring a snapshot, must expose a - function call operator with the following signature to load entities: - - ```cpp - void operator()(Entity &); - ``` - - Where `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 either component or - tag for each type to be restored. Therefore, given a type `T`, the archive - must contain a function call operator with the following signature: - - ```cpp - void operator()(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++`](https://uscilab.github.io/cereal/) 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: - -```cpp -entt::DefaultRegistry registry; -entt::DefaultPrototype prototype{registry}; - -prototype.set(100.f, 100.f); -prototype.set(0.f, 0.f); - -// ... - -const auto entity = 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: - ```cpp - const auto entity = prototype(); - ``` - It is equivalent to the following invokation: - ```cpp - const auto entity = prototype.create(); - ``` - -* In case we want to initialize an already existing entity, we can provide the - `operator()` directly with the entity identifier: - ```cpp - prototype(entity); - ``` - It is equivalent to the following invokation: - ```cpp - prototype.assign(entity); - ``` - 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: - ```cpp - prototype.accommodate(entity); - ``` - -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 `AType` and `AnotherType` whenever `MyType` is -assigned to an entity: - -```cpp -entt::connnect(registry.construction()); -``` - -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: - -```cpp -entt::disconnect(registry.construction()); -``` - -#### Labels - -There's nothing magical about the way labels 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::label`. - -If used in combination with hashed strings, it helps to use labels where types -would be required otherwise. As an example: - -```cpp -registry.assign>(entity); -``` - -### 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: - -```cpp -registry.valid(entt::null); -``` - -In other terms, a registry will reject the null entity in all cases because it -isn't considered valid. It means that the null entity cannot own components or -tags 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: - -```cpp -typename entt::DefaultRegistry::entity_type null = entt::null; -``` - -Similarly, the null entity can be compared to any other identifier: - -```cpp -const auto entity = registry.create(); -const bool null = (entity == entt::null); -``` - -## View: to persist or not to persist? - -First of all, it is worth answering an obvious question: why views?
-Roughly speaking, 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 can only iterate entities and their components, then read or update the -data members of the latter.
-It is a subtle difference that can help designing a better software sometimes. - -There are mainly four kinds of views: standard (also known as `View`), -persistent (also known as `PersistentView`), raw (also known as `RawView`) and -runtime (also known as `RuntimeView`).
-All of them have pros and cons to take in consideration. In particular: - -* Standard views: - - Pros: - - * They work out-of-the-box and don't require any dedicated data structure. - * Creating and destroying them isn't expensive at all because they don't have - any type of initialization. - * They are the best tool for iterating entities for a single component. - * They are the best tool for iterating entities for multiple components when - one of the components is assigned to a significantly low number of entities. - * They don't affect any other operations of the registry. - - Cons: - - * Their performance tend to degenerate when the number of components to - iterate grows up and the most of the entities have all of them. - -* Persistent views: - - Pros: - - * Once prepared, creating and destroying them isn't expensive at all because - they don't have any type of initialization. - * They are the best tool for iterating entities for multiple components when - most entities have them all. - - Cons: - - * They have dedicated data structures and thus affect the memory usage to a - minimal extent. - * If not previously prepared, the first time they are used they go through an - initialization step that could take a while. - * They affect to a minimum the creation and destruction of entities and - components. In other terms: the more persistent views there will be, the - less performing will be creating and destroying entities and components. - -* Raw views: - - Pros: - - * They work out-of-the-box and don't require any dedicated data structure. - * Creating and destroying them isn't expensive at all because they don't have - any type of initialization. - * They are the best tool for iterating components when it is not necessary to - know which entities they belong to. - * They don't affect any other operations of the registry. - - Cons: - - * They can be used to iterate only one type of component at a time. - * They don't return the entity to which a component belongs to the caller. - -* Runtime views: - - Pros: - - * Their lists of components are defined at runtime and not at compile-time. - * Creating and destroying them isn't expensive at all because they don't have - any type of initialization. - * They are the best tool for things like plugin systems and mods in general. - * They don't affect any other operations of the registry. - - Cons: - - * Their performances are definitely lower than those of all the other views, - although they are still usable and sufficient for most of the purposes. - -To sum up and as a rule of thumb: - -* Use a raw view to iterate components only (no entities) for a given type. -* Use a standard view to iterate entities and components for a single type. -* Use a standard view to iterate entities and components for multiple types when - the number of types is low. Standard views are really optimized and persistent - views won't add much in this case. -* Use a standard view to iterate entities and components for multiple types when - a significantly low number of entities have one of the components. -* Use a standard view in all those cases where a persistent view would give a - boost to performance but the iteration isn't performed frequently. -* Prepare and use a persistent view when you want to iterate only entities for - multiple components. -* Prepare and use a persistent view when you want to iterate entities for - multiple components and each component is assigned to a great number of - entities but the intersection between the sets of entities is small. -* Prepare and use a persistent view in all the cases where a standard view - wouldn't fit well otherwise. -* Finally, in case you don't know at compile-time what are the components to - use, choose a runtime view and set them during execution. - -To easily iterate entities and components, all the views offer the common -`begin` and `end` member functions that allow users to use a view in a typical -range-for loop. Almost all the views offer also a *more functional* `each` -member function that accepts a callback for convenience.
-Continue reading for more details or refer to the inline documentation. - -### Standard View - -A standard view behaves differently if it's constructed for a single component -or if it has been requested to iterate multiple components. Even the API is -different in the two cases.
-All that they share is the way they are created by means of a registry: - -```cpp -// single component standard view -auto single = registry.view(); - -// multi component standard view -auto multi = registry.view(); -``` - -For all that remains, it's worth discussing them separately.
- -#### Single component standard view - -Single component standard views are specialized in order to give a boost in -terms of performance in all the situation. This kind of views can access the -underlying data structures directly and avoid superfluous checks.
-They offer a bunch of functionalities to get the number of entities they are -going to return and a raw access to the entity list as well as to the component -list. It's also possible to ask a view if it contains a given entity.
-Refer to the inline documentation for all the details. - -There is no need to store views around for they are extremely cheap to -construct, even though they can be copied without problems and reused freely. In -fact, they return newly created and correctly initialized iterators whenever -`begin` or `end` are invoked.
-To iterate a single component standard view, either use it in a range-for loop: - -```cpp -auto view = registry.view(); - -for(auto entity: view) { - Renderable &renderable = view.get(entity); - - // ... -} -``` - -Or rely on the `each` member function to iterate entities and get all their -components at once: - -```cpp -registry.view().each([](auto entity, auto &renderable) { - // ... -}); -``` - -The `each` member function is highly optimized. Unless users want to iterate -only entities, using `each` should be the preferred approach. - -**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. - -#### Multi component standard view - -Multi component standard 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 fewer functionalities than their companion views for single -component. In particular, a multi component standard view exposes utility -functions to get the estimated number of entities it is going to return and to -know whether it's empty or not. It's also possible to ask a view if it contains -a given entity.
-Refer to the inline documentation for all the details. - -There is no need to store views around for they are extremely cheap to -construct, even though they can be copied without problems and reused freely. In -fact, they return newly created and correctly initialized iterators whenever -`begin` or `end` are invoked.
-To iterate a multi component standard view, either use it in a range-for loop: - -```cpp -auto view = registry.view(); - -for(auto entity: view) { - // a component at a time ... - Position &position = view.get(entity); - Velocity &velocity = view.get(entity); - - // ... or multiple components at once - std::tuple tup = view.get(entity); - - // ... -} -``` - -Or rely on the `each` member function to iterate entities and get all their -components at once: - -```cpp -registry.view().each([](auto entity, auto &position, auto &velocity) { - // ... -}); -``` - -The `each` member function is highly optimized. Unless users want to iterate -only entities or get only some of the components, using `each` should be the -preferred approach. - -**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. - -### Persistent View - -A persistent view 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, persistent views don't stay true to the order of any set of -components unless users explicitly sort them. - -Persistent views can be used only to iterate multiple components. To create this -kind of views, the tag `persistent_t` must also be used in order to disambiguate -overloads of the `view` member function: - -```cpp -auto view = registry.view(entt::persistent_t{}); -``` - -There is no need to store views around for they are extremely cheap to -construct, even though they can be copied without problems and reused freely. In -fact, they return newly created and correctly initialized iterators whenever -`begin` or `end` are invoked.
-That being said, persistent views perform an initialization step the very first -time they are constructed and this could be quite costly. To avoid it, consider -asking to the registry to _prepare_ them when no entities have been created yet: - -```cpp -registry.prepare(); -``` - -If the registry is empty, preparation is extremely fast. Moreover the `prepare` -member function template is idempotent. Feel free to invoke it even more than -once: if the view has been already prepared before, the function returns -immediately and does nothing. - -A persistent view offers a bunch of functionalities to get the number of -entities it's going to return, a raw access to the entity list and the -possibility to sort the underlying data structures according to the order of one -of the components for which it has been constructed. It's also possible to ask a -view if it contains a given entity.
-Refer to the inline documentation for all the details. - -To iterate a persistent view, either use it in a range-for loop: - -```cpp -auto view = registry.view(entt::persistent_t{}); - -for(auto entity: view) { - // a component at a time ... - Position &position = view.get(entity); - Velocity &velocity = view.get(entity); - - // ... or multiple components at once - std::tuple tup = view.get(entity); - - // ... -} -``` - -Or rely on the `each` member function to iterate entities and get all their -components at once: - -```cpp -registry.view(entt::persistent_t{}).each([](auto entity, auto &position, auto &velocity) { - // ... -}); -``` - -Performance are more or less the same. The best approach depends mainly on -whether all the components have to be accessed or not. - -**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. - -### Raw View - -Raw views return all the components of a given type. This kind of views can -access components directly and avoid extra indirections like when components are -accessed via an entity identifier.
-They offer a bunch of functionalities to get the number of instances they are -going to return and a raw access to the entity list as well as to the component -list.
-Refer to the inline documentation for all the details. - -Raw views can be used only to iterate components for a single type. To create -this kind of views, the tag `raw_t` must also be used in order to disambiguate -overloads of the `view` member function: - -```cpp -auto view = registry.view(entt::raw_t{}); -``` - -There is no need to store views around for they are extremely cheap to -construct, even though they can be copied without problems and reused freely. In -fact, they return newly created and correctly initialized iterators whenever -`begin` or `end` are invoked.
-To iterate a raw view, use it in a range-for loop: - -```cpp -auto view = registry.view(entt::raw_t{}); - -for(auto &&component: raw) { - // ... -} -``` - -Or rely on the `each` member function: - -```cpp -registry.view(entt::raw_t{}).each([](auto &renderable) { - // ... -}); -``` - -Performance are exactly the same in both cases. - -**Note**: raw views don't have a `get` member function for obvious reasons. - -### Runtime View - -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 standard -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 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: - -```cpp -using component_type = typename decltype(registry)::component_type; -component_type types[] = { registry.type(), registry.type() }; - -auto view = registry.view(std::cbegin(types), std::cend(types)); - -for(auto entity: view) { - // a component at a time ... - Position &position = registry.get(entity); - Velocity &velocity = registry.get(entity); - - // ... or multiple components at once - std::tuple tup = view.get(entity); - - // ... -} -``` - -Or rely on the `each` member function to iterate entities: - -```cpp -using component_type = typename decltype(registry)::component_type; -component_type types[] = { registry.type(), registry.type() }; - -auto view = registry.view(std::cbegin(types), std::cend(types)).each([](auto entity) { - // ... -}); -``` - -Performance are exactly the same in both cases. - -**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. - - -### Give me everything - -Views 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: - -```cpp -registry.each([](auto entity) { - // ... -}); -``` - -It returns to the caller all the entities that are still in use by means of the -given function.
-As a rule of thumb, consider using a view if the goal is to iterate entities -that have a determinate set of components. A view is usually much faster than -combining this function with a bunch of custom tests.
-In all the other cases, this is the way to go. - -There exists also another member function to use to retrieve orphans. An orphan -is an entity that is still in use and has neither assigned components nor -tags.
-The signature of the function is the same of `each`: - -```cpp -registry.orphans([](auto entity) { - // ... -}); -``` - -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. - -## Iterations: what is allowed and what is not - -Most of the _ECS_ available out there have some annoying limitations (at least -from my point of view): entities and components cannot be created nor destroyed -during iterations.
-`EnTT` partially solves the problem with a few limitations: - -* Creating entities and components is allowed during iterations. -* Deleting an entity or removing its components is allowed during iterations if - it's the one currently returned by the view. For all the other entities, - destroying them or removing their components isn't allowed and it can result - in undefined behavior. - -Iterators are invalidated and the behavior is undefined if an entity is modified -or destroyed and it's not the one currently returned by the view nor a newly -created one.
-To work around it, possible approaches are: - -* Store aside the entities and the components to be removed and perform the - operations at the end of the iteration. -* 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. - -## 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 and consequently the approach adopted by `EnTT` are the great exception to -the rule. It's true that views and thus their iterators 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: - -* As long as a thread iterates the entities that have the component `X` or - assign and removes that component from a set of entities, another thread can - safely do the same with components `Y` and `Z` and everything will work like a - charm. As a trivial example, users can freely execute the rendering system and - iterate the renderable entities while updating a physic component concurrently - on a separate thread. - -* Similarly, a single set of components can be iterated by multiple threads as - long as the components are neither assigned nor removed in the meantime. In - other words, a hypothetical movement system can start multiple threads, each - of which will access the components that carry information about velocity and - position for its entities. - -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. - -# Crash Course: core functionalities - -`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: - -```cpp -#include - -// defines the identifiers for the given types -using ID = entt::Identifier; - -// ... - -switch(aTypeIdentifier) { -case ID::get(): - // ... - break; -case ID::get(): - // ... - break; -default: - // ... -} -``` - -This is all what the class template has to offer: a static `get` member function -that returns 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: - -```cpp -template struct IgnoreType {}; - -using ID = entt::Identifier< - ATypeStillValid, - IgnoreType, - AnotherTypeStillValid ->; -``` - -A bit ugly to see, but it works at least. - -## Runtime identifiers - -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: - -```cpp -using component_family = entt::Family; - -// ... - -template -component_type component() const noexcept { - return component_family::type(); -} -``` - -This is all what a _family_ has to offer: a `type` member function that returns -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 resource 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. - -Example of use: - -```cpp -auto load(entt::HashedString::hash_type resource) { - // uses the numeric representation of the resource to load and return it -} - -auto resource = load(entt::HashedString{"gui/background"}); -``` - -There is also a _user defined literal_ dedicated to hashed strings to make them -more user-friendly: - -```cpp -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 resource 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 `int`s or -`bool`s. 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. - -Example of use: - -```cpp -entt::Monostate{} = true; -entt::Monostate<"mykey"_hs>{} = 42; - -// ... - -const bool b = entt::Monostate<"mykey"_hs>{}; -const int i = entt::Monostate{}; -``` - -# Crash Course: service locator - -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 the user knows how to -handle it. - -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: - -```cpp -// the service has no base type, a locator is used to treat it as a kind of singleton -entt::ServiceLocator::set(params...); - -// sets up an opaque service -entt::ServiceLocator::set(params...); - -// resets (destroys) the service -entt::ServiceLocator::reset(); -``` - -The locator can also be queried to know if an active service is currently set -and to retrieve it if necessary (either as a pointer or as a reference): - -```cpp -// no service currently set -auto empty = entt::ServiceLocator::empty(); - -// gets a (possibly empty) shared pointer to the service ... -std::shared_ptr ptr = entt::ServiceLocator::get(); - -// ... or a reference, but it's undefined behaviour if the service isn't set yet -AudioInterface &ref = entt::ServiceLocator::ref(); -``` - -A common use is to wrap the different locators in a container class, creating -aliases for the various services: - -```cpp -struct Locator { - using Camera = entt::ServiceLocator; - using Audio = entt::ServiceLocator; - // ... -}; - -// ... - -void init() { - Locator::Camera::set(); - Locator::Audio::set(params...); - // ... -} -``` - -# Crash Course: cooperative scheduler - -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): - -* `void update(Delta, void *);` - - It's invoked once per tick until a process is explicitly aborted or it - terminates either with or without errors. Even though it's not mandatory to - declare this member function, as a rule of thumb each process should at - least define it to work properly. The `void *` parameter is an opaque pointer - to user data (if any) forwarded directly to the process during an update. - -* `void init(void *);` - - It's invoked at the first tick, immediately before an update. The `void *` - parameter is an opaque pointer to user data (if any) forwarded directly to the - process during an update. - -* `void succeeded();` - - It's invoked in case of success, immediately after an update and during the - same tick. - -* `void failed();` - - It's invoked in case of errors, immediately after an update and during the - same tick. - -* `void aborted();` - - It's invoked only if a process is explicitly aborted. There is no guarantee - that it executes in the same tick, this depends solely on whether the - process is aborted immediately or not. - -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: - -```cpp -struct MyProcess: entt::Process { - using delta_type = std::uint32_t; - - void update(delta_type delta, void *) { - remaining = delta > remaining ? delta_type{] : (remaining - delta); - - // ... - - if(!remaining) { - succeed(); - } - } - - void init(void *data) { - remaining = *static_cast(data); - } - -private: - delta_type remaining; -}; -``` - -### Adaptor - -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. - -The function call operator has a signature similar to the one of the `update` -function of a process but for the fact that it receives two extra arguments to -call whenever a process is terminated with success or with an error: - -```cpp -void(Delta delta, void *data, auto succeed, auto fail); -``` - -Parameters have the following meaning: - -* `delta` is the elapsed time. -* `data` is an opaque pointer to user data if any, `nullptr` otherwise. -* `succeed` is a function to call when a process terminates with success. -* `fail` is a function to call when a process terminates with errors. - -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 once more the next tick.
-A process can also have a child. In this case, the process is replaced with -its child when it terminates if it returns with success. In case of errors, -both the process and its child are discarded. This way, it's easy to create -chain of processes to run sequentially. - -Using a scheduler is straightforward. To create it, users must provide only the -type for the elapsed times and no arguments at all: - -```cpp -Scheduler scheduler; -``` - -It has member functions to query its internal data structures, like `empty` or -`size`, as well as a `clear` utility to reset it to a clean state: - -```cpp -// checks if there are processes still running -const auto empty = scheduler.empty(); - -// gets the number of processes still running -Scheduler::size_type size = scheduler.size(); - -// resets the scheduler to its initial state and discards all the processes -scheduler.clear(); -``` - -To attach a process to a scheduler there are mainly two ways: - -* If the process inherits from the `Process` class template, it's enough to - indicate its type and submit all the parameters required to construct it to - the `attach` member function: - - ```cpp - scheduler.attach("foobar"); - ``` - -* Otherwise, in case of a lambda or a functor, it's enough to provide an - instance of the class to the `attach` member function: - - ```cpp - scheduler.attach([](auto...){ /* ... */ }); - ``` - -In both cases, the return value is an opaque object that offers a `then` member -function to use to create chains of processes to run sequentially.
-As a minimal example of use: - -```cpp -// schedules a task in the form of a lambda function -scheduler.attach([](auto delta, void *, auto succeed, auto fail) { - // ... -}) -// appends a child in the form of another lambda function -.then([](auto delta, void *, auto succeed, auto fail) { - // ... -}) -// appends a child in the form of a process class -.then(); -``` - -To update a scheduler and thus all its processes, the `update` member function -is the way to go: - -```cpp -// updates all the processes, no user data are provided -scheduler.update(delta); - -// updates all the processes and provides them with custom data -scheduler.update(delta, &data); -``` - -In addition to these functions, the scheduler offers an `abort` member function -that can be used to discard all the running processes at once: - -```cpp -// aborts all the processes abruptly ... -scheduler.abort(true); - -// ... or gracefully during the next tick -scheduler.abort(); -``` - -# Crash Course: resource management - -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 the user wants it to be. An image, a video, an audio, -whatever. There are no limits.
-As a minimal example: - -```cpp -struct MyResource { const int value; }; -``` - -A _loader_ is a class the aim of which is to load a specific resource. It has to -inherit directly from the dedicated base class as in the following example: - -```cpp -struct MyLoader final: entt::ResourceLoader { - // ... -}; -``` - -Where `MyResource` is the type of resources it creates.
-A resource loader must also expose a public const member function named `load` -that accepts a variable number of arguments and returns a shared pointer to a -resource.
-As an example: - -```cpp -struct MyLoader: entt::ResourceLoader { - std::shared_ptr load(int value) const { - // ... - return std::shared_ptr(new MyResource{ value }); - } -}; -``` - -In general, resource loaders should not have a state or retain data of any type. -They should let the cache manage their resources instead.
-As a side note, base class and CRTP idiom aren't strictly required with the -current implementation. One could argue that a cache can easily work with -loaders of any type. However, future changes won't be breaking ones by forcing -the use of a base class today and that's why the model is already in its place. - -Finally, a cache is a specialization of a class template tailored to a specific -resource: - -```cpp -using MyResourceCache = entt::ResourceCache; - -// ... - -MyResourceCache cache{}; -``` - -The idea is to create different caches for different types of resources and to -manage each one independently and in the most appropriate way.
-As a (very) trivial example, audio tracks can survive in most of the scenes of -an application while meshes can be associated with a single scene and then -discarded when the user leaves it. - -A cache offers a set of basic functionalities to query its internal state and to -_organize_ it: - -```cpp -// gets the number of resources managed by a cache -const auto size = cache.size(); - -// checks if a cache contains at least a valid resource -const auto empty = cache.empty(); - -// clears a cache and discards its content -cache.clear(); -``` - -Besides these member functions, it 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: - -```cpp -entt::ResourceCache::resource_type -``` - -Where `resource_type` is an alias for `entt::HashedString`. Therefore, resource -identifiers are created explicitly as in the following example: - -```cpp -constexpr auto identifier = entt::ResourceCache::resource_type{"my/resource/identifier"}; -// this is equivalent to the following -constexpr auto hs = entt::HashedString{"my/resource/identifier"}; -``` - -The class `HashedString` is described in a dedicated section, so I won't do 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: - -```cpp -// uses the identifier declared above -cache.load(identifier, 0); - -// uses a const char * directly as an identifier -cache.load("another/identifier", 42); -``` - -The return value can be used to know if the resource has been loaded correctly. -In case the loader returns an invalid pointer or the resource already exists in -the cache, a false value is returned: - -```cpp -if(!cache.load("another/identifier", 42)) { - // ... -} -``` - -Unfortunately, in this case there is no way to know what was the problem -exactly. However, before trying to load a resource or after an error, one can -use the `contains` member function to know if a cache already contains a -specific resource: - -```cpp -auto exists = cache.contains("my/identifier"); -``` - -There exists also a member function to use to force a reload of an already -existing resource if needed: - -```cpp -auto result = cache.reload("another/identifier", 42); -``` - -As above, the function returns true in case of success, false otherwise. The -sole difference in this case is that an error necessarily means that the loader -has failed for some reasons to load the resource.
-Note that the `reload` member function is a kind of alias of the following -snippet: - -```cpp -cache.discard(identifier); -cache.load(identifier, 42); -``` - -Where the `discard` member function is used to get rid of a resource if loaded. -In case the cache doesn't contain a resource for the given identifier, the -function does nothing and returns immediately. - -So far, so good. Resources are finally loaded and stored within the cache.
-They are returned to users in the form of handles. To get one of them: - -```cpp -auto handle = cache.handle("my/identifier"); -``` - -The idea behind a handle is the same of the flyweight pattern. In other terms, -resources aren't copied around. Instead, instances are shared between handles. -Users of a resource owns a handle and it guarantees that a resource isn't -destroyed until all the handles are destroyed, even if the resource itself is -removed from the cache.
-Handles are tiny objects both movable and copyable. They returns the contained -resource as a const reference on request: - -* By means of the `get` member function: - - ```cpp - const auto &resource = handle.get(); - ``` - -* Using the proper cast operator: - - ```cpp - const auto &resource = handle; - ``` - -* Through the dereference operator: - - ```cpp - const auto &resource = *handle; - ``` - -The resource can also be accessed directly using the arrow operator if required: - -```cpp -auto value = handle->value; -``` - -To test if a handle is still valid, the cast operator to `bool` allows users to -use it in a guard: - -```cpp -if(handle) { - // ... -} -``` - -Finally, in case there is the need to load a resource and thus to get a handle -without storing the resource itself in the cache, users can rely on the `temp` -member function template.
-The declaration is similar to the one of `load` but for the fact that it doesn't -return a boolean value. Instead, it returns a (possibly invalid) handle for the -resource: - -```cpp -auto handle = cache.temp("another/identifier", 42); -``` - -Do not forget to test the handle for validity. Otherwise, getting the reference -to the resource it points may result in undefined behavior. - -# Crash Course: events, signals and everything in between - -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. - -## Signals - -Signal handlers work with naked pointers, function pointers and pointers to -member functions. Listeners can be any kind of objects and the user is 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: - -```cpp -// no collector type -entt::SigH signal; - -// explicit collector type -entt::SigH> collector; -``` - -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`). - -Besides them, there are member functions to use both to connect and disconnect -listeners in all their forms by means of a sink: - -```cpp -void foo(int, char) { /* ... */ } - -struct S { - void bar(int, char) { /* ... */ } -}; - -// ... - -S instance; - -signal.sink().connect<&foo>(); -signal.sink().connect(&instance); - -// ... - -// disconnects a free function -signal.sink().disconnect<&foo>(); - -// disconnect a specific member function of an instance ... -signal.sink().disconnect(&instance); - -// ... or an instance as a whole -signal.sink().disconnect(&instance); - -// discards all the listeners at once -signal.sink().disconnect(); -``` - -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: - -```cpp -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: - -```cpp -struct MyCollector { - std::vector vec{}; - - bool operator()(int v) noexcept { - vec.push_back(v); - return true; - } -}; - -int f() { return 0; } -int g() { return 1; } - -// ... - -entt::SigH> signal; - -signal.sink().connect<&f>(); -signal.sink().connect<&g>(); - -MyCollector collector = signal.collect(); - -assert(collector.vec[0] == 0); -assert(collector.vec[1] == 1); -``` - -As shown above, 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. - -## Delegate - -A delegate can be used as general purpose invoker with no memory overhead for -free functions and member functions 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. - -The interface is trivial. It offers a default constructor to create empty -delegates: - -```cpp -entt::Delegate delegate{}; -``` - -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. - -Attempting to use an empty delegate by invoking its function call operator -results in undefined behavior, most likely a crash actually. Before to use a -delegate, it must be initialized.
-There exist two functions to do that, both named `connect`: - -```cpp -int f(int i) { return i; } - -struct MyStruct { - int f(int i) { return i } -}; - -// bind a free function to the delegate -delegate.connect<&f>(); - -// bind a member function to the delegate -MyStruct instance; -delegate.connect(&instance); -``` - -It hasn't a `disconnect` counterpart. Instead, there exists a `reset` member -function to clear it.
-The `empty` member function can be used to know if a delegate is empty: - -```cpp -const auto empty = delegate.empty(); -``` - -Finally, to invoke a delegate, the function call operator is the way to go as -usual: - -```cpp -auto ret = delegate(42); -``` - -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. - -## Event dispatcher - -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: - -```cpp -// define a general purpose dispatcher that works with naked pointers -entt::Dispatcher dispatcher{}; -``` - -In order to register an instance of a class to a dispatcher, its type must -expose one or more member functions of which the return types are `void` and the -argument lists are `const E &`, for each type of event `E`.
-To ease the development, member functions that are named `receive` are -automatically detected and have not to be explicitly specified when registered. -In all the other cases, the name of the member function aimed to receive the -event must be provided to the `connect` member function of the sink bound to the -specific event: - -```cpp -struct AnEvent { int value; }; -struct AnotherEvent {}; - -struct Listener -{ - void receive(const AnEvent &) { /* ... */ } - void method(const AnotherEvent &) { /* ... */ } -}; - -// ... - -Listener listener; -dispatcher.sink().connect(&listener); -dispatcher.sink().connect(&listener); -``` - -The `disconnect` member function follows the same pattern and can be used to -selectively remove listeners: - -```cpp -dispatcher.sink().disconnect(&listener); -dispatcher.sink().disconnect(&listener); -``` - -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 the user 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: - -```cpp -dispatcher.trigger(42); -dispatcher.trigger(); -``` - -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`: - -```cpp -dispatcher.enqueue(42); -dispatcher.enqueue(); -``` - -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: - -```cpp -// emits all the events of the given type at once -dispatcher.update(); - -// 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`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in -modern C++), it was adapted later to be included in this library. - -To create a custom emitter type, derived classes must inherit directly from the -base class as: - -```cpp -struct MyEmitter: Emitter { - // ... -} -``` - -The full list of accepted types of events isn't required. Handlers are created -internally on the fly and thus each type of event is accepted by default. - -Whenever an event is published, an emitter provides the listeners with a -reference to itself along with a const reference to the event. Therefore -listeners have an handy way to work with it without incurring in the need of -capturing a reference to the emitter itself.
-In addition, an opaque object is returned each time a connection is established -between an emitter and a listener, allowing the caller to disconnect them at a -later time.
-The opaque object used to handle connections is both movable and copyable. On -the other side, an event emitter is movable but not copyable by default. - -To create new instances of an emitter, no arguments are required: - -```cpp -MyEmitter emitter{}; -``` - -Listeners must be movable and callable objects (free functions, lambdas, -functors, `std::function`s, whatever) whose function type is: - -```cpp -void(const Event &, MyEmitter &) -``` - -Where `Event` is the type of event they want to listen.
-There are two ways to attach a listener to an event emitter that differ -slightly from each other: - -* To register a long-lived listener, use the `on` member function. It is meant - to register a listener designed to be invoked more than once for the given - event type.
- As an example: - - ```cpp - auto conn = emitter.on([](const MyEvent &event, MyEmitter &emitter) { - // ... - }); - ``` - - The connection object can be freely discarded. Otherwise, it can be used later - to disconnect the listener if required. - -* To register a short-lived listener, use the `once` member function. It is - meant to register a listener designed to be invoked only once for the given - event type. The listener is automatically disconnected after the first - invocation.
- As an example: - - ```cpp - auto conn = emitter.once([](const MyEvent &event, MyEmitter &emitter) { - // ... - }); - ``` - - The connection object can be freely discarded. Otherwise, it can be used later - to disconnect the listener if required. - -In both cases, the connection object can be used with the `erase` member -function: - -```cpp -emitter.erase(conn); -``` - -There are also two member functions to use either to disconnect all the -listeners for a given type of event or to clear the emitter: - -```cpp -// removes all the listener for the specific event -emitter.clear(); - -// removes all the listeners registered so far -emitter.clear(); -``` - -To send an event to all the listeners that are interested in it, the `publish` -member function offers a convenient approach that relieves the user from having -to create the event: - -```cpp -struct MyEvent { int i; }; - -// ... - -emitter.publish(42); -``` - -Finally, the `empty` member function tests if there exists at least either a -listener registered with the event emitter or to a given type of event: - -```cpp -bool empty; - -// checks if there is any listener registered for the specific event -empty = emitter.empty(); - -// checks it there are listeners registered with the event emitter -empty = emitter.empty(); -``` - -In general, the event emitter is a handy tool when the derived classes _wrap_ -asynchronous operations, because it introduces a _nice-to-have_ model based on -events and listeners that kindly hides the complexity behind the scenes. However -it is not limited to such uses. - # Packaging Tools `EnTT` is available for some of the most known packaging tools. In particular: diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 8a93b478c..ee4897a44 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -20,3 +20,17 @@ install( DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/ ) + +add_custom_target( + docs_aob + SOURCES + CONTRIBUTING.md + core.md + entity.md + locator.md + process.md + resource.md + shared.md + signal.md + extra.dox +) diff --git a/docs/core.md b/docs/core.md new file mode 100644 index 000000000..b4b2f115f --- /dev/null +++ b/docs/core.md @@ -0,0 +1,167 @@ +# Crash Course: core functionalities + + +# Table of Contents + +* [Introduction](#introduction) +* [Compile-time identifiers](#compile-time-identifiers) +* [Runtime identifiers](#runtime-identifiers) +* [Hashed strings](#hashed-strings) + * [Conflicts](#conflicts) +* [Monostate](#monostate) + + +# 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: + +```cpp +#include + +// defines the identifiers for the given types +using ID = entt::Identifier; + +// ... + +switch(aTypeIdentifier) { +case ID::get(): + // ... + break; +case ID::get(): + // ... + break; +default: + // ... +} +``` + +This is all what the class template has to offer: a static `get` member function +that returns 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: + +```cpp +template struct IgnoreType {}; + +using ID = entt::Identifier< + ATypeStillValid, + IgnoreType, + AnotherTypeStillValid +>; +``` + +A bit ugly to see, but it works at least. + +# Runtime identifiers + +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: + +```cpp +using component_family = entt::Family; + +// ... + +template +component_type component() const noexcept { + return component_family::type(); +} +``` + +This is all what a _family_ has to offer: a `type` member function that returns +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 resource 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. + +Example of use: + +```cpp +auto load(entt::HashedString::hash_type resource) { + // uses the numeric representation of the resource to load and return it +} + +auto resource = load(entt::HashedString{"gui/background"}); +``` + +There is also a _user defined literal_ dedicated to hashed strings to make them +more user-friendly: + +```cpp +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 resource 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 `int`s or +`bool`s. 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. + +Example of use: + +```cpp +entt::Monostate{} = true; +entt::Monostate<"mykey"_hs>{} = 42; + +// ... + +const bool b = entt::Monostate<"mykey"_hs>{}; +const int i = entt::Monostate{}; +``` diff --git a/docs/entity.md b/docs/entity.md new file mode 100644 index 000000000..aefb74a07 --- /dev/null +++ b/docs/entity.md @@ -0,0 +1,1400 @@ +# Crash Course: entity-component system + + +# Table of Contents + +* [Introduction](#introduction) +* [Design choices](#design-choices) + * [A bitset-free entity-component system](#a-bitset-free-entity-component-system) + * [Pay per use](#pay-per-use) +* [Vademecum](#vademecum) +* [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component) + * [Single instance components](#single-instance-components) + * [Observe changes](#observe-changes) + * [Who let the tags out?](#who-let-the-tags-out) + * [Runtime components](#runtime-components) + * [A journey through a plugin](#a-journey-through-a-plugin) + * [Sorting: is it possible?](#sorting-is-it-possible) + * [Snapshot: complete vs continuous](#snapshot-complete-vs-continuous) + * [Snapshot loader](#snapshot-loader) + * [Continuous loader](#continuous-loader) + * [Archives](#archives) + * [One example to rule them all](#one-example-to-rule-them-all) + * [Prototype](#prototype) + * [Helpers](#helpers) + * [Dependency function](#dependency-function) + * [Labels](#labels) + * [Null entity](#null-entity) +* [View: to persist or not to persist?](#view-to-persist-or-not-to-persist) + * [Standard View](#standard-view) + * [Single component standard view](#single-component-standard-view) + * [Multi component standard view](#multi-component-standard-view) + * [Persistent View](#persistent-view) + * [Raw View](#raw-view) + * [Runtime View](#runtime-view) + * [Give me everything](#give-me-everything) +* [Iterations: what is allowed and what is not](#iterations-what-is-allowed-and-what-is-not) +* [Multithreading](#multithreading) + + +# Introduction + +`EnTT` is a header-only, tiny and easy to use entity-component system (and much +more) written in modern C++.
+The entity-component-system (also known as _ECS_) is an architectural pattern +used mostly in game development. + +# Design choices + +## 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.
+This is why users can instantiate the core class simply like: + +```cpp +entt::DefaultRegistry registry; +``` + +In place of its more annoying and error-prone counterpart: + +```cpp +entt::DefaultRegistry registry; +``` + +## 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. +However, slightly worse performance along non-critical paths are the right price +to pay to reduce memory usage and I've always wondered why this kind of tools do +not leave me the choice.
+`EnTT` follows a completely different approach. It squeezes the best from the +basic data structures and gives users the possibility to pay more for higher +performance where needed.
+The disadvantage of this approach is that users need to know the systems they +are working on and the tools they are using. Otherwise, the risk to ruin the +performance along critical paths is high. + +So far, this choice has proven to be a good one and I really hope it can be for +many others besides me. + +# Vademecum + +The `Registry` to store, the views 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 the `DefaultRegistry` is nothing more than a +`Registry` where the type of the entities is `std::uint32_t`).
+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` or a view 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.
+In fact, the project is composed of many other classes in addition to those +describe below. For more details, please refer to the inline documentation. + +# The Registry, the Entity and the Component + +A registry can store and manage entities, as well as create views to iterate the +underlying data structures.
+`Registry` is a class template that 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 an alias named `DefaultRegistry` for +`Registry`. + +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 destroy entities: + +```cpp +// 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); +``` + +Entities can also be destroyed _by type_, that is by specifying the types of the +tags or components that identify them: + +```cpp +// destroys the entity that owns the given tag, if any +registry.destroy(entt::tag_t{}); + +// destroys the entities that own the given components, if any +registry.destroy(); +``` + +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 get out of the them all the +information they carry: + +```cpp +// 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: + +```cpp +registry.assign(entity, 0., 0.); + +// ... + +Velocity &velocity = registry.assign(entity); +velocity.dx = 0.; +velocity.dy = 0.; +``` + +If an entity already has the given component, the `replace` member function +template can be used to replace it: + +```cpp +registry.replace(entity, 0., 0.); + +// ... + +Velocity &velocity = registry.replace(entity); +velocity.dx = 0.; +velocity.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, `accommodate` 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): + +```cpp +registry.accommodate(entity, 0., 0.); + +// ... + +Velocity &velocity = registry.accommodate(entity); +velocity.dx = 0.; +velocity.dy = 0.; +``` + +Note that `accommodate` is a slightly faster alternative for the following +`if/else` statement and nothing more: + +```cpp +if(registry.has(entity)) { + registry.replace(entity, arg1, argN); +} else { + registry.assign(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: + +```cpp +bool b = registry.has(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: + +```cpp +registry.remove(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: + +```cpp +registry.reset(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: + + ```cpp + registry.reset(); + ``` + +* If neither the entity nor the component are specified, all the entities still + in use and their components are destroyed: + + ```cpp + registry.reset(); + ``` + +Finally, references to components can be retrieved simply by doing this: + +```cpp +const auto &cregistry = registry; + +// const and non-const reference +const Position &position = cregistry.get(entity); +Position &position = registry.get(entity); + +// const and non-const references +std::tuple tup = cregistry.get(entity); +std::tuple tup = registry.get(entity); +``` + +The `get` member function template gives direct access to the component of an +entity stored in the underlying data structures of the registry. + +## Single instance components + +In those cases where all what is needed is a single instance component, tags are +the right tool to achieve the purpose.
+Tags undergo the same requirements of components. They can be either plain old +data structures or more complex and movable data structures with a proper +constructor.
+Actually, the same type can be used both as a tag and as a component and the +registry will not complain about it. It is up to users to properly manage their +own types. In some cases, the tag `tag_t` must also be used in order to +disambiguate overloads of member functions. + +Attaching tags to entities and removing them is trivial: + +```cpp +auto player = registry.create(); +auto camera = registry.create(); + +// attaches a default-initialized tag to an entity +registry.assign(entt::tag_t{}, player); + +// attaches a tag to an entity and initializes it +registry.assign(entt::tag_t{}, camera, player); + +// removes tags from their owners +registry.remove(); +registry.remove(); +``` + +In case a tag already has an owner, its content can be updated by means of the +`replace` member function template and the ownership of the tag can be +transferred to another entity using the `move` member function template: + +``` +// replaces the content of the given tag +Point &point = registry.replace(entt::tag_t{}, 1.f, 1.f); + +// transfers the ownership of the tag to another entity +entity_type prev = registry.move(next); +``` + +If in doubt about whether or not a tag already has an owner, the `has` member +function template may be useful: + +```cpp +bool b = registry.has(); +``` + +References to tags can be retrieved simply by doing this: + +```cpp +const auto &cregistry = registry; + +// either a non-const reference ... +PlayingCharacter &player = registry.get(); + +// ... or a const one +const Camera &camera = cregistry.get(); +``` + +The `get` member function template gives direct access to the tag as stored in +the underlying data structures of the registry. + +As shown above, in almost all the cases the entity identifier isn't required. +Since a single instance component can have only one associated entity, it +doesn't make much sense to mention it explicitly.
+To find out who the owner is, just do the following: + +```cpp +auto player = registry.attachee(); +``` + +Note that iterating tags isn't possible for obvious reasons. Tags give direct +access to single entities and nothing more. + +## Observe changes + +Because of how the registry works internally, it stores a couple of signal +handlers for each pool in order to notify some of its data structures on the +construction and destruction of components.
+These signal handlers are also exposed and made available to users. This is the +basic brick 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 `construction` member function: + +```cpp +// connects a free function +registry.construction().connect<&MyFreeFunction>(); + +// connects a member function +registry.construction().connect(&instance); + +// disconnects a free function +registry.construction().disconnect<&MyFreeFunction>(); + +// disconnects a member function +registry.construction().disconnect(&instance); +``` + +To be notified when components are destroyed, use the `destruction` member +function instead. + +The function type of a listener is the same in both cases: + +```cpp +void(Registry &, Entity); +``` + +In other terms, a listener is provided with the registry that triggered the +notification and the entity affected by the change. Note also that: + +* Listeners are invoked **after** components have been assigned to entities. +* Listeners are invoked **before** components have been removed from entities. +* The order of invocation of the listeners isn't guaranteed in any case. + +There are also some limitations on what a listener can and cannot do. In +particular: + +* Connecting and disconnecting other functions from within the body of a + listener should be avoided. It can lead to undefined behavior in some cases. +* Assigning and removing components and tags from within the body of a listener + that observes the destruction of instances of a given type should be avoided. + It can lead to undefined behavior in some cases. This type of listeners is + intended to provide users with an easy way to perform cleanup and nothing + more. + +To a certain extent, these limitations do not apply. However, it is 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. + +### Who let the tags out? + +As an extension, signals are also provided with tags. Although they are not +strictly required internally, it makes sense that a user expects signal support +even when it comes to tags actually.
+Signals for tags undergo exactly the same requirements of those introduced for +components. Also the function type for a listener is the same and it's invoked +with the same guarantees discussed above. + +To get the sinks for a tag just use tag `tag_t` to disambiguate overloads of +member functions as in the following example: + +```cpp +registry.construction(entt::tag_t{}).connect<&MyFreeFunction>(); +registry.destruction(entt::tag_t{}).connect(&instance); +``` + +Listeners for tags and components are managed separately and do not influence +each other in any case. Therefore, note that the greater the number of listeners +for a type, the greater the performance hit when a tag of the given type is +created or destroyed. + +## 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. + +In `EnTT`, identifiers are easily accessible: + +```cpp +entt::DefaultRegistry registry; + +// standard component identifier +auto ctype = registry.type(); + +// single instance component identifier +auto ttype = registry.type(entt::tag_t{}); +``` + +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`](https://github.com/svaarala/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.
+Note also that this isn't neither the only nor (probably) the best way to do it. +In fact, the right way depends on the scripting language and the problem one is +facing in general.
+That being said, feel free to use it at your own risk. + +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: + +* Components can be sorted directly: + + ```cpp + registry.sort([](const auto &lhs, const auto &rhs) { + return lhs.z < rhs.z; + + }); + ``` + + 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 pattern of usage 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: + + ```cpp + registry.sort(); + ``` + + In this case, instances of `Movement` are arranged in memory so that cache + misses are minimized when the two components are iterated together. + +## Snapshot: complete vs continuous + +The `Registry` class offers basic support to serialization.
+It doesn't convert components and tags 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. + +The goal of the serialization part is to allow users to make both a dump of the +entire registry or a narrower snapshot, that is to select only the components +and the tags in which they are interested.
+Intuitively, the use cases are different. As an example, the first approach is +suitable for local save/restore functionalities while the latter is suitable for +creating client-server applications and for transferring somehow parts of the +representation side to side. + +To take a snapshot of the registry, use the `snapshot` member function. It +returns a temporary object properly initialized to _save_ the whole registry or +parts of it. + +Example of use: + +```cpp +OutputArchive output; + +registry.snapshot() + .entities(output) + .destroyed(output) + .component(output) + .tag(output); +``` + +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. + +The `entities` member function asks the registry to serialize all the entities +that are still in use along with their versions. On the other side, the +`destroyed` member function tells to the registry to serialize the entities that +have been destroyed and are no longer in use.
+These two functions can be used to save and restore the whole set of entities +with the versions they had during serialization. + +The `component` member function is a function template the aim of which is to +store aside components. The presence of a template parameter list is a +consequence of a couple of design choices from the past and in the present: + +* First of all, there is no reason to force a user to serialize all the + components at once and most of the times it isn't desiderable. As an example, + in case the stuff for the HUD in a game is put into the registry for some + reasons, its components can be freely discarded during a serialization step + because probably the software already knows how to reconstruct the HUD + correctly from scratch. + +* Furthermore, the registry makes heavy use of _type-erasure_ techniques + internally and doesn't know at any time what types of components it contains. + Therefore being explicit at the call point is mandatory. + +There exists also another version of the `component` member function that +accepts a range of entities to serialize. This version is a bit slower than the +other one, mainly because it iterates the range of entities more than once for +internal purposes. However, it can be used to filter out those entities that +shouldn't be serialized for some reasons.
+As an example: + +```cpp +const auto view = registry.view(); +OutputArchive output; + +registry.snapshot() + .component(output, view.cbegin(), view.cend()); +``` + +The `tag` member function is similar to the previous one, apart from the fact +that it works with tags and not with components.
+Note also that both `component` and `tag` store items along with entities. It +means that they work 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 `restore` that returns a +temporary object properly initialized to _restore_ a snapshot. + +Example of use: + +```cpp +InputArchive input; + +registry.restore() + .entities(input) + .destroyed(input) + .component(input) + .tag(input) + .orphans(); +``` + +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 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 same applies to the +`tag` member function. + +The `orphans` member function literally destroys those entities that have +neither components nor tags. 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 and tags are saved, it could happen that some of the entities have +neither components nor tags 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 or tags (as an example, as +data members or gathered in a container).
+Another difference with the snapshot loader is that the continuous loader does +not need to work with the private data structures of a registry. Furthermore, it +has an internal state that must persist over time. Therefore, there is no reason +to create it by means of a registry, or to limit its lifetime to that of a +temporary object. + +Example of use: + +```cpp +entt::ContinuousLoader loader{registry}; +InputArchive input; + +loader.entities(input) + .destroyed(input) + .component(input, &DirtyComponent::parent, &DirtyComponent::child) + .tag(input, &DirtyTag::container) + .orphans() + .shrink(); +``` + +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` and `tag` member functions restore all and only the components +and the tags specified and assign them to the right entities.
+In case the component or the tag contains entities itself (either as data +members of type `entity_type` 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 +neither components nor tags 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: + + ```cpp + void operator()(Entity); + ``` + + Where `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 either component or + tag for each type to be serialized. Therefore, given a type `T`, the archive + must contain a function call operator with the following signature: + + ```cpp + void operator()(Entity, const T &); + ``` + + The output archive can freely decide how to serialize the data. The register + is not affected at all by the decision. + +* An input archive, the one used when restoring a snapshot, must expose a + function call operator with the following signature to load entities: + + ```cpp + void operator()(Entity &); + ``` + + Where `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 either component or + tag for each type to be restored. Therefore, given a type `T`, the archive + must contain a function call operator with the following signature: + + ```cpp + void operator()(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++`](https://uscilab.github.io/cereal/) 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: + +```cpp +entt::DefaultRegistry registry; +entt::DefaultPrototype prototype{registry}; + +prototype.set(100.f, 100.f); +prototype.set(0.f, 0.f); + +// ... + +const auto entity = 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: + ```cpp + const auto entity = prototype(); + ``` + It is equivalent to the following invokation: + ```cpp + const auto entity = prototype.create(); + ``` + +* In case we want to initialize an already existing entity, we can provide the + `operator()` directly with the entity identifier: + ```cpp + prototype(entity); + ``` + It is equivalent to the following invokation: + ```cpp + prototype.assign(entity); + ``` + 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: + ```cpp + prototype.accommodate(entity); + ``` + +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 `AType` and `AnotherType` whenever `MyType` is +assigned to an entity: + +```cpp +entt::connnect(registry.construction()); +``` + +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: + +```cpp +entt::disconnect(registry.construction()); +``` + +### Labels + +There's nothing magical about the way labels 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::label`. + +If used in combination with hashed strings, it helps to use labels where types +would be required otherwise. As an example: + +```cpp +registry.assign>(entity); +``` + +## 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: + +```cpp +registry.valid(entt::null); +``` + +In other terms, a registry will reject the null entity in all cases because it +isn't considered valid. It means that the null entity cannot own components or +tags 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: + +```cpp +typename entt::DefaultRegistry::entity_type null = entt::null; +``` + +Similarly, the null entity can be compared to any other identifier: + +```cpp +const auto entity = registry.create(); +const bool null = (entity == entt::null); +``` + +# View: to persist or not to persist? + +First of all, it is worth answering an obvious question: why views?
+Roughly speaking, 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 can only iterate entities and their components, then read or update the +data members of the latter.
+It is a subtle difference that can help designing a better software sometimes. + +There are mainly four kinds of views: standard (also known as `View`), +persistent (also known as `PersistentView`), raw (also known as `RawView`) and +runtime (also known as `RuntimeView`).
+All of them have pros and cons to take in consideration. In particular: + +* Standard views: + + Pros: + + * They work out-of-the-box and don't require any dedicated data structure. + * Creating and destroying them isn't expensive at all because they don't have + any type of initialization. + * They are the best tool for iterating entities for a single component. + * They are the best tool for iterating entities for multiple components when + one of the components is assigned to a significantly low number of entities. + * They don't affect any other operations of the registry. + + Cons: + + * Their performance tend to degenerate when the number of components to + iterate grows up and the most of the entities have all of them. + +* Persistent views: + + Pros: + + * Once prepared, creating and destroying them isn't expensive at all because + they don't have any type of initialization. + * They are the best tool for iterating entities for multiple components when + most entities have them all. + + Cons: + + * They have dedicated data structures and thus affect the memory usage to a + minimal extent. + * If not previously prepared, the first time they are used they go through an + initialization step that could take a while. + * They affect to a minimum the creation and destruction of entities and + components. In other terms: the more persistent views there will be, the + less performing will be creating and destroying entities and components. + +* Raw views: + + Pros: + + * They work out-of-the-box and don't require any dedicated data structure. + * Creating and destroying them isn't expensive at all because they don't have + any type of initialization. + * They are the best tool for iterating components when it is not necessary to + know which entities they belong to. + * They don't affect any other operations of the registry. + + Cons: + + * They can be used to iterate only one type of component at a time. + * They don't return the entity to which a component belongs to the caller. + +* Runtime views: + + Pros: + + * Their lists of components are defined at runtime and not at compile-time. + * Creating and destroying them isn't expensive at all because they don't have + any type of initialization. + * They are the best tool for things like plugin systems and mods in general. + * They don't affect any other operations of the registry. + + Cons: + + * Their performances are definitely lower than those of all the other views, + although they are still usable and sufficient for most of the purposes. + +To sum up and as a rule of thumb: + +* Use a raw view to iterate components only (no entities) for a given type. +* Use a standard view to iterate entities and components for a single type. +* Use a standard view to iterate entities and components for multiple types when + the number of types is low. Standard views are really optimized and persistent + views won't add much in this case. +* Use a standard view to iterate entities and components for multiple types when + a significantly low number of entities have one of the components. +* Use a standard view in all those cases where a persistent view would give a + boost to performance but the iteration isn't performed frequently. +* Prepare and use a persistent view when you want to iterate only entities for + multiple components. +* Prepare and use a persistent view when you want to iterate entities for + multiple components and each component is assigned to a great number of + entities but the intersection between the sets of entities is small. +* Prepare and use a persistent view in all the cases where a standard view + wouldn't fit well otherwise. +* Finally, in case you don't know at compile-time what are the components to + use, choose a runtime view and set them during execution. + +To easily iterate entities and components, all the views offer the common +`begin` and `end` member functions that allow users to use a view in a typical +range-for loop. Almost all the views offer also a *more functional* `each` +member function that accepts a callback for convenience.
+Continue reading for more details or refer to the inline documentation. + +## Standard View + +A standard view behaves differently if it's constructed for a single component +or if it has been requested to iterate multiple components. Even the API is +different in the two cases.
+All that they share is the way they are created by means of a registry: + +```cpp +// single component standard view +auto single = registry.view(); + +// multi component standard view +auto multi = registry.view(); +``` + +For all that remains, it's worth discussing them separately.
+ +### Single component standard view + +Single component standard views are specialized in order to give a boost in +terms of performance in all the situation. This kind of views can access the +underlying data structures directly and avoid superfluous checks.
+They offer a bunch of functionalities to get the number of entities they are +going to return and a raw access to the entity list as well as to the component +list. It's also possible to ask a view if it contains a given entity.
+Refer to the inline documentation for all the details. + +There is no need to store views around for they are extremely cheap to +construct, even though they can be copied without problems and reused freely. In +fact, they return newly created and correctly initialized iterators whenever +`begin` or `end` are invoked.
+To iterate a single component standard view, either use it in a range-for loop: + +```cpp +auto view = registry.view(); + +for(auto entity: view) { + Renderable &renderable = view.get(entity); + + // ... +} +``` + +Or rely on the `each` member function to iterate entities and get all their +components at once: + +```cpp +registry.view().each([](auto entity, auto &renderable) { + // ... +}); +``` + +The `each` member function is highly optimized. Unless users want to iterate +only entities, using `each` should be the preferred approach. + +**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. + +### Multi component standard view + +Multi component standard 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 fewer functionalities than their companion views for single +component. In particular, a multi component standard view exposes utility +functions to get the estimated number of entities it is going to return and to +know whether it's empty or not. It's also possible to ask a view if it contains +a given entity.
+Refer to the inline documentation for all the details. + +There is no need to store views around for they are extremely cheap to +construct, even though they can be copied without problems and reused freely. In +fact, they return newly created and correctly initialized iterators whenever +`begin` or `end` are invoked.
+To iterate a multi component standard view, either use it in a range-for loop: + +```cpp +auto view = registry.view(); + +for(auto entity: view) { + // a component at a time ... + Position &position = view.get(entity); + Velocity &velocity = view.get(entity); + + // ... or multiple components at once + std::tuple tup = view.get(entity); + + // ... +} +``` + +Or rely on the `each` member function to iterate entities and get all their +components at once: + +```cpp +registry.view().each([](auto entity, auto &position, auto &velocity) { + // ... +}); +``` + +The `each` member function is highly optimized. Unless users want to iterate +only entities or get only some of the components, using `each` should be the +preferred approach. + +**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. + +## Persistent View + +A persistent view 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, persistent views don't stay true to the order of any set of +components unless users explicitly sort them. + +Persistent views can be used only to iterate multiple components. To create this +kind of views, the tag `persistent_t` must also be used in order to disambiguate +overloads of the `view` member function: + +```cpp +auto view = registry.view(entt::persistent_t{}); +``` + +There is no need to store views around for they are extremely cheap to +construct, even though they can be copied without problems and reused freely. In +fact, they return newly created and correctly initialized iterators whenever +`begin` or `end` are invoked.
+That being said, persistent views perform an initialization step the very first +time they are constructed and this could be quite costly. To avoid it, consider +asking to the registry to _prepare_ them when no entities have been created yet: + +```cpp +registry.prepare(); +``` + +If the registry is empty, preparation is extremely fast. Moreover the `prepare` +member function template is idempotent. Feel free to invoke it even more than +once: if the view has been already prepared before, the function returns +immediately and does nothing. + +A persistent view offers a bunch of functionalities to get the number of +entities it's going to return, a raw access to the entity list and the +possibility to sort the underlying data structures according to the order of one +of the components for which it has been constructed. It's also possible to ask a +view if it contains a given entity.
+Refer to the inline documentation for all the details. + +To iterate a persistent view, either use it in a range-for loop: + +```cpp +auto view = registry.view(entt::persistent_t{}); + +for(auto entity: view) { + // a component at a time ... + Position &position = view.get(entity); + Velocity &velocity = view.get(entity); + + // ... or multiple components at once + std::tuple tup = view.get(entity); + + // ... +} +``` + +Or rely on the `each` member function to iterate entities and get all their +components at once: + +```cpp +registry.view(entt::persistent_t{}).each([](auto entity, auto &position, auto &velocity) { + // ... +}); +``` + +Performance are more or less the same. The best approach depends mainly on +whether all the components have to be accessed or not. + +**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. + +## Raw View + +Raw views return all the components of a given type. This kind of views can +access components directly and avoid extra indirections like when components are +accessed via an entity identifier.
+They offer a bunch of functionalities to get the number of instances they are +going to return and a raw access to the entity list as well as to the component +list.
+Refer to the inline documentation for all the details. + +Raw views can be used only to iterate components for a single type. To create +this kind of views, the tag `raw_t` must also be used in order to disambiguate +overloads of the `view` member function: + +```cpp +auto view = registry.view(entt::raw_t{}); +``` + +There is no need to store views around for they are extremely cheap to +construct, even though they can be copied without problems and reused freely. In +fact, they return newly created and correctly initialized iterators whenever +`begin` or `end` are invoked.
+To iterate a raw view, use it in a range-for loop: + +```cpp +auto view = registry.view(entt::raw_t{}); + +for(auto &&component: raw) { + // ... +} +``` + +Or rely on the `each` member function: + +```cpp +registry.view(entt::raw_t{}).each([](auto &renderable) { + // ... +}); +``` + +Performance are exactly the same in both cases. + +**Note**: raw views don't have a `get` member function for obvious reasons. + +## Runtime View + +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 standard +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 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: + +```cpp +using component_type = typename decltype(registry)::component_type; +component_type types[] = { registry.type(), registry.type() }; + +auto view = registry.view(std::cbegin(types), std::cend(types)); + +for(auto entity: view) { + // a component at a time ... + Position &position = registry.get(entity); + Velocity &velocity = registry.get(entity); + + // ... or multiple components at once + std::tuple tup = view.get(entity); + + // ... +} +``` + +Or rely on the `each` member function to iterate entities: + +```cpp +using component_type = typename decltype(registry)::component_type; +component_type types[] = { registry.type(), registry.type() }; + +auto view = registry.view(std::cbegin(types), std::cend(types)).each([](auto entity) { + // ... +}); +``` + +Performance are exactly the same in both cases. + +**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. + + +## Give me everything + +Views 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: + +```cpp +registry.each([](auto entity) { + // ... +}); +``` + +It returns to the caller all the entities that are still in use by means of the +given function.
+As a rule of thumb, consider using a view if the goal is to iterate entities +that have a determinate set of components. A view is usually much faster than +combining this function with a bunch of custom tests.
+In all the other cases, this is the way to go. + +There exists also another member function to use to retrieve orphans. An orphan +is an entity that is still in use and has neither assigned components nor +tags.
+The signature of the function is the same of `each`: + +```cpp +registry.orphans([](auto entity) { + // ... +}); +``` + +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. + +# Iterations: what is allowed and what is not + +Most of the _ECS_ available out there have some annoying limitations (at least +from my point of view): entities and components cannot be created nor destroyed +during iterations.
+`EnTT` partially solves the problem with a few limitations: + +* Creating entities and components is allowed during iterations. +* Deleting an entity or removing its components is allowed during iterations if + it's the one currently returned by the view. For all the other entities, + destroying them or removing their components isn't allowed and it can result + in undefined behavior. + +Iterators are invalidated and the behavior is undefined if an entity is modified +or destroyed and it's not the one currently returned by the view nor a newly +created one.
+To work around it, possible approaches are: + +* Store aside the entities and the components to be removed and perform the + operations at the end of the iteration. +* 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. + +# 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 and consequently the approach adopted by `EnTT` are the great exception to +the rule. It's true that views and thus their iterators 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: + +* As long as a thread iterates the entities that have the component `X` or + assign and removes that component from a set of entities, another thread can + safely do the same with components `Y` and `Z` and everything will work like a + charm. As a trivial example, users can freely execute the rendering system and + iterate the renderable entities while updating a physic component concurrently + on a separate thread. + +* Similarly, a single set of components can be iterated by multiple threads as + long as the components are neither assigned nor removed in the meantime. In + other words, a hypothetical movement system can start multiple threads, each + of which will access the components that carry information about velocity and + position for its entities. + +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. diff --git a/docs/locator.md b/docs/locator.md new file mode 100644 index 000000000..ed5773b05 --- /dev/null +++ b/docs/locator.md @@ -0,0 +1,75 @@ +# Crash Course: service locator + + +# Table of Contents + +* [Introduction](#introduction) +* [Service locator](#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: + +```cpp +// the service has no base type, a locator is used to treat it as a kind of singleton +entt::ServiceLocator::set(params...); + +// sets up an opaque service +entt::ServiceLocator::set(params...); + +// resets (destroys) the service +entt::ServiceLocator::reset(); +``` + +The locator can also be queried to know if an active service is currently set +and to retrieve it if necessary (either as a pointer or as a reference): + +```cpp +// no service currently set +auto empty = entt::ServiceLocator::empty(); + +// gets a (possibly empty) shared pointer to the service ... +std::shared_ptr ptr = entt::ServiceLocator::get(); + +// ... or a reference, but it's undefined behaviour if the service isn't set yet +AudioInterface &ref = entt::ServiceLocator::ref(); +``` + +A common use is to wrap the different locators in a container class, creating +aliases for the various services: + +```cpp +struct Locator { + using Camera = entt::ServiceLocator; + using Audio = entt::ServiceLocator; + // ... +}; + +// ... + +void init() { + Locator::Camera::set(); + Locator::Audio::set(params...); + // ... +} +``` diff --git a/docs/process.md b/docs/process.md new file mode 100644 index 000000000..abfaaaf95 --- /dev/null +++ b/docs/process.md @@ -0,0 +1,211 @@ +# Crash Course: cooperative scheduler + + +# Table of Contents + +* [Introduction](#introduction) +* [The process](#the-process) + * [Adaptor](#adaptor) +* [The scheduler](#the-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): + +* `void update(Delta, void *);` + + It's invoked once per tick until a process is explicitly aborted or it + terminates either with or without errors. Even though it's not mandatory to + declare this member function, as a rule of thumb each process should at + least define it to work properly. The `void *` parameter is an opaque pointer + to user data (if any) forwarded directly to the process during an update. + +* `void init(void *);` + + It's invoked at the first tick, immediately before an update. The `void *` + parameter is an opaque pointer to user data (if any) forwarded directly to the + process during an update. + +* `void succeeded();` + + It's invoked in case of success, immediately after an update and during the + same tick. + +* `void failed();` + + It's invoked in case of errors, immediately after an update and during the + same tick. + +* `void aborted();` + + It's invoked only if a process is explicitly aborted. There is no guarantee + that it executes in the same tick, this depends solely on whether the + process is aborted immediately or not. + +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: + +```cpp +struct MyProcess: entt::Process { + using delta_type = std::uint32_t; + + void update(delta_type delta, void *) { + remaining = delta > remaining ? delta_type{] : (remaining - delta); + + // ... + + if(!remaining) { + succeed(); + } + } + + void init(void *data) { + remaining = *static_cast(data); + } + +private: + delta_type remaining; +}; +``` + +## Adaptor + +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. + +The function call operator has a signature similar to the one of the `update` +function of a process but for the fact that it receives two extra arguments to +call whenever a process is terminated with success or with an error: + +```cpp +void(Delta delta, void *data, auto succeed, auto fail); +``` + +Parameters have the following meaning: + +* `delta` is the elapsed time. +* `data` is an opaque pointer to user data if any, `nullptr` otherwise. +* `succeed` is a function to call when a process terminates with success. +* `fail` is a function to call when a process terminates with errors. + +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 once more the next tick.
+A process can also have a child. In this case, the process is replaced with +its child when it terminates if it returns with success. In case of errors, +both the process and its child are discarded. This way, it's easy to create +chain of processes to run sequentially. + +Using a scheduler is straightforward. To create it, users must provide only the +type for the elapsed times and no arguments at all: + +```cpp +Scheduler scheduler; +``` + +It has member functions to query its internal data structures, like `empty` or +`size`, as well as a `clear` utility to reset it to a clean state: + +```cpp +// checks if there are processes still running +const auto empty = scheduler.empty(); + +// gets the number of processes still running +Scheduler::size_type size = scheduler.size(); + +// resets the scheduler to its initial state and discards all the processes +scheduler.clear(); +``` + +To attach a process to a scheduler there are mainly two ways: + +* If the process inherits from the `Process` class template, it's enough to + indicate its type and submit all the parameters required to construct it to + the `attach` member function: + + ```cpp + scheduler.attach("foobar"); + ``` + +* Otherwise, in case of a lambda or a functor, it's enough to provide an + instance of the class to the `attach` member function: + + ```cpp + scheduler.attach([](auto...){ /* ... */ }); + ``` + +In both cases, the return value is an opaque object that offers a `then` member +function to use to create chains of processes to run sequentially.
+As a minimal example of use: + +```cpp +// schedules a task in the form of a lambda function +scheduler.attach([](auto delta, void *, auto succeed, auto fail) { + // ... +}) +// appends a child in the form of another lambda function +.then([](auto delta, void *, auto succeed, auto fail) { + // ... +}) +// appends a child in the form of a process class +.then(); +``` + +To update a scheduler and thus all its processes, the `update` member function +is the way to go: + +```cpp +// updates all the processes, no user data are provided +scheduler.update(delta); + +// updates all the processes and provides them with custom data +scheduler.update(delta, &data); +``` + +In addition to these functions, the scheduler offers an `abort` member function +that can be used to discard all the running processes at once: + +```cpp +// aborts all the processes abruptly ... +scheduler.abort(true); + +// ... or gracefully during the next tick +scheduler.abort(); +``` diff --git a/docs/resource.md b/docs/resource.md new file mode 100644 index 000000000..f74b22c1a --- /dev/null +++ b/docs/resource.md @@ -0,0 +1,240 @@ +# Crash Course: resource management + + +# Table of Contents + +* [Introduction](#introduction) +* [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache) + + +# 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.
+As a minimal example: + +```cpp +struct MyResource { const int value; }; +``` + +A _loader_ is a class the aim of which is to load a specific resource. It has to +inherit directly from the dedicated base class as in the following example: + +```cpp +struct MyLoader final: entt::ResourceLoader { + // ... +}; +``` + +Where `MyResource` is the type of resources it creates.
+A resource loader must also expose a public const member function named `load` +that accepts a variable number of arguments and returns a shared pointer to a +resource.
+As an example: + +```cpp +struct MyLoader: entt::ResourceLoader { + std::shared_ptr load(int value) const { + // ... + return std::shared_ptr(new MyResource{ value }); + } +}; +``` + +In general, resource loaders should not have a state or retain data of any type. +They should let the cache manage their resources instead.
+As a side note, base class and CRTP idiom aren't strictly required with the +current implementation. One could argue that a cache can easily work with +loaders of any type. However, future changes won't be breaking ones by forcing +the use of a base class today and that's why the model is already in its place. + +Finally, a cache is a specialization of a class template tailored to a specific +resource: + +```cpp +using MyResourceCache = entt::ResourceCache; + +// ... + +MyResourceCache cache{}; +``` + +The idea is to create different caches for different types of resources and to +manage each one independently and in the most appropriate way.
+As a (very) trivial example, audio tracks can survive in most of the scenes of +an application while meshes can be associated with a single scene and then +discarded when users leave it. + +A cache offers a set of basic functionalities to query its internal state and to +_organize_ it: + +```cpp +// gets the number of resources managed by a cache +const auto size = cache.size(); + +// checks if a cache contains at least a valid resource +const auto empty = cache.empty(); + +// clears a cache and discards its content +cache.clear(); +``` + +Besides these member functions, it 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: + +```cpp +entt::ResourceCache::resource_type +``` + +Where `resource_type` is an alias for `entt::HashedString`. Therefore, resource +identifiers are created explicitly as in the following example: + +```cpp +constexpr auto identifier = entt::ResourceCache::resource_type{"my/resource/identifier"}; +// this is equivalent to the following +constexpr auto hs = entt::HashedString{"my/resource/identifier"}; +``` + +The class `HashedString` is described in a dedicated section, so I won't do 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: + +```cpp +// uses the identifier declared above +cache.load(identifier, 0); + +// uses a const char * directly as an identifier +cache.load("another/identifier", 42); +``` + +The return value can be used to know if the resource has been loaded correctly. +In case the loader returns an invalid pointer or the resource already exists in +the cache, a false value is returned: + +```cpp +if(!cache.load("another/identifier", 42)) { + // ... +} +``` + +Unfortunately, in this case there is no way to know what was the problem +exactly. However, before trying to load a resource or after an error, one can +use the `contains` member function to know if a cache already contains a +specific resource: + +```cpp +auto exists = cache.contains("my/identifier"); +``` + +There exists also a member function to use to force a reload of an already +existing resource if needed: + +```cpp +auto result = cache.reload("another/identifier", 42); +``` + +As above, the function returns true in case of success, false otherwise. The +sole difference in this case is that an error necessarily means that the loader +has failed for some reasons to load the resource.
+Note that the `reload` member function is a kind of alias of the following +snippet: + +```cpp +cache.discard(identifier); +cache.load(identifier, 42); +``` + +Where the `discard` member function is used to get rid of a resource if loaded. +In case the cache doesn't contain a resource for the given identifier, the +function does nothing and returns immediately. + +So far, so good. Resources are finally loaded and stored within the cache.
+They are returned to users in the form of handles. To get one of them: + +```cpp +auto handle = cache.handle("my/identifier"); +``` + +The idea behind a handle is the same of the flyweight pattern. In other terms, +resources aren't copied around. Instead, instances are shared between handles. +Users of a resource owns a handle and it guarantees that a resource isn't +destroyed until all the handles are destroyed, even if the resource itself is +removed from the cache.
+Handles are tiny objects both movable and copyable. They returns the contained +resource as a const reference on request: + +* By means of the `get` member function: + + ```cpp + const auto &resource = handle.get(); + ``` + +* Using the proper cast operator: + + ```cpp + const auto &resource = handle; + ``` + +* Through the dereference operator: + + ```cpp + const auto &resource = *handle; + ``` + +The resource can also be accessed directly using the arrow operator if required: + +```cpp +auto value = handle->value; +``` + +To test if a handle is still valid, the cast operator to `bool` allows users to +use it in a guard: + +```cpp +if(handle) { + // ... +} +``` + +Finally, in case there is the need to load a resource and thus to get a handle +without storing the resource itself in the cache, users can rely on the `temp` +member function template.
+The declaration is similar to the one of `load` but for the fact that it doesn't +return a boolean value. Instead, it returns a (possibly invalid) handle for the +resource: + +```cpp +auto handle = cache.temp("another/identifier", 42); +``` + +Do not forget to test the handle for validity. Otherwise, getting the reference +to the resource it points may result in undefined behavior. diff --git a/docs/shared.md b/docs/shared.md new file mode 100644 index 000000000..fdcc056dd --- /dev/null +++ b/docs/shared.md @@ -0,0 +1,39 @@ +### EnTT and shared libraries + +To make sure that an application and a shared library that use both `EnTT` can +interact correctly when symbols are hidden by default, there are some tricks to +follow.
+In particular and in order to avoid undefined behaviors, all the instantiation +of the `Family` class template shall be made explicit along with the system-wide +specifier to use to export them. + +At the time I'm writing this document, the classes that use internally the above +mentioned class template are `Dispatcher`, `Emitter` and `Registry`. Therefore +and as an example, if you use the `Registry` class template in your shared +library and want to set symbols visibility to _hidden_ by default, the following +lines are required to allow it to function properly with a client that also uses +the `Registry` somehow: + +* On GNU/Linux: + + ```cpp + namespace entt { + template class __attribute__((visibility("default"))) Family; + template class __attribute__((visibility("default"))) Family; + template class __attribute__((visibility("default"))) Family; + } + ``` + +* On Windows: + + ```cpp + namespace entt { + template class __declspec(dllexport) Family; + template class __declspec(dllexport) Family; + template class __declspec(dllexport) Family; + } + ``` + +Otherwise, the risk is that type identifiers are different between the shared +library and the application and this will prevent the whole thing from +functioning correctly for obvious reasons. diff --git a/docs/signal.md b/docs/signal.md new file mode 100644 index 000000000..e14aaee59 --- /dev/null +++ b/docs/signal.md @@ -0,0 +1,412 @@ +# Crash Course: events, signals and everything in between + + +# Table of Contents + +* [Introduction](#introduction) +* [Signals](#signals) +* [Delegate](#delegate) +* [Event dispatcher](#event-dispatcher) +* [Event emitter](#event-emitter) + + +# 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. + +# 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: + +```cpp +// no collector type +entt::SigH signal; + +// explicit collector type +entt::SigH> collector; +``` + +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`). + +Besides them, there are member functions to use both to connect and disconnect +listeners in all their forms by means of a sink: + +```cpp +void foo(int, char) { /* ... */ } + +struct S { + void bar(int, char) { /* ... */ } +}; + +// ... + +S instance; + +signal.sink().connect<&foo>(); +signal.sink().connect(&instance); + +// ... + +// disconnects a free function +signal.sink().disconnect<&foo>(); + +// disconnect a specific member function of an instance ... +signal.sink().disconnect(&instance); + +// ... or an instance as a whole +signal.sink().disconnect(&instance); + +// discards all the listeners at once +signal.sink().disconnect(); +``` + +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: + +```cpp +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: + +```cpp +struct MyCollector { + std::vector vec{}; + + bool operator()(int v) noexcept { + vec.push_back(v); + return true; + } +}; + +int f() { return 0; } +int g() { return 1; } + +// ... + +entt::SigH> signal; + +signal.sink().connect<&f>(); +signal.sink().connect<&g>(); + +MyCollector collector = signal.collect(); + +assert(collector.vec[0] == 0); +assert(collector.vec[1] == 1); +``` + +As shown above, 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. + +# Delegate + +A delegate can be used as general purpose invoker with no memory overhead for +free functions and member functions 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. + +The interface is trivial. It offers a default constructor to create empty +delegates: + +```cpp +entt::Delegate delegate{}; +``` + +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. + +Attempting to use an empty delegate by invoking its function call operator +results in undefined behavior, most likely a crash actually. Before to use a +delegate, it must be initialized.
+There exist two functions to do that, both named `connect`: + +```cpp +int f(int i) { return i; } + +struct MyStruct { + int f(int i) { return i } +}; + +// bind a free function to the delegate +delegate.connect<&f>(); + +// bind a member function to the delegate +MyStruct instance; +delegate.connect(&instance); +``` + +It hasn't a `disconnect` counterpart. Instead, there exists a `reset` member +function to clear it.
+The `empty` member function can be used to know if a delegate is empty: + +```cpp +const auto empty = delegate.empty(); +``` + +Finally, to invoke a delegate, the function call operator is the way to go as +usual: + +```cpp +auto ret = delegate(42); +``` + +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. + +# Event dispatcher + +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: + +```cpp +// define a general purpose dispatcher that works with naked pointers +entt::Dispatcher dispatcher{}; +``` + +In order to register an instance of a class to a dispatcher, its type must +expose one or more member functions of which the return types are `void` and the +argument lists are `const E &`, for each type of event `E`.
+To ease the development, member functions that are named `receive` are +automatically detected and have not to be explicitly specified when registered. +In all the other cases, the name of the member function aimed to receive the +event must be provided to the `connect` member function of the sink bound to the +specific event: + +```cpp +struct AnEvent { int value; }; +struct AnotherEvent {}; + +struct Listener +{ + void receive(const AnEvent &) { /* ... */ } + void method(const AnotherEvent &) { /* ... */ } +}; + +// ... + +Listener listener; +dispatcher.sink().connect(&listener); +dispatcher.sink().connect(&listener); +``` + +The `disconnect` member function follows the same pattern and can be used to +selectively remove listeners: + +```cpp +dispatcher.sink().disconnect(&listener); +dispatcher.sink().disconnect(&listener); +``` + +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: + +```cpp +dispatcher.trigger(42); +dispatcher.trigger(); +``` + +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`: + +```cpp +dispatcher.enqueue(42); +dispatcher.enqueue(); +``` + +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: + +```cpp +// emits all the events of the given type at once +dispatcher.update(); + +// 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`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in +modern C++), it was adapted later to be included in this library. + +To create a custom emitter type, derived classes must inherit directly from the +base class as: + +```cpp +struct MyEmitter: Emitter { + // ... +} +``` + +The full list of accepted types of events isn't required. Handlers are created +internally on the fly and thus each type of event is accepted by default. + +Whenever an event is published, an emitter provides the listeners with a +reference to itself along with a const reference to the event. Therefore +listeners have an handy way to work with it without incurring in the need of +capturing a reference to the emitter itself.
+In addition, an opaque object is returned each time a connection is established +between an emitter and a listener, allowing the caller to disconnect them at a +later time.
+The opaque object used to handle connections is both movable and copyable. On +the other side, an event emitter is movable but not copyable by default. + +To create new instances of an emitter, no arguments are required: + +```cpp +MyEmitter emitter{}; +``` + +Listeners must be movable and callable objects (free functions, lambdas, +functors, `std::function`s, whatever) whose function type is: + +```cpp +void(const Event &, MyEmitter &) +``` + +Where `Event` is the type of event they want to listen.
+There are two ways to attach a listener to an event emitter that differ +slightly from each other: + +* To register a long-lived listener, use the `on` member function. It is meant + to register a listener designed to be invoked more than once for the given + event type.
+ As an example: + + ```cpp + auto conn = emitter.on([](const MyEvent &event, MyEmitter &emitter) { + // ... + }); + ``` + + The connection object can be freely discarded. Otherwise, it can be used later + to disconnect the listener if required. + +* To register a short-lived listener, use the `once` member function. It is + meant to register a listener designed to be invoked only once for the given + event type. The listener is automatically disconnected after the first + invocation.
+ As an example: + + ```cpp + auto conn = emitter.once([](const MyEvent &event, MyEmitter &emitter) { + // ... + }); + ``` + + The connection object can be freely discarded. Otherwise, it can be used later + to disconnect the listener if required. + +In both cases, the connection object can be used with the `erase` member +function: + +```cpp +emitter.erase(conn); +``` + +There are also two member functions to use either to disconnect all the +listeners for a given type of event or to clear the emitter: + +```cpp +// removes all the listener for the specific event +emitter.clear(); + +// removes all the listeners registered so far +emitter.clear(); +``` + +To send an event to all the listeners that are interested in it, the `publish` +member function offers a convenient approach that relieves users from having to +create the event: + +```cpp +struct MyEvent { int i; }; + +// ... + +emitter.publish(42); +``` + +Finally, the `empty` member function tests if there exists at least either a +listener registered with the event emitter or to a given type of event: + +```cpp +bool empty; + +// checks if there is any listener registered for the specific event +empty = emitter.empty(); + +// checks it there are listeners registered with the event emitter +empty = emitter.empty(); +``` + +In general, the event emitter is a handy tool when the derived classes _wrap_ +asynchronous operations, because it introduces a _nice-to-have_ model based on +events and listeners that kindly hides the complexity behind the scenes. However +it is not limited to such uses.