From a5a4ee4bfe21327e930a7512289e110ef1b4eab0 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Mon, 10 Jun 2019 00:24:08 +0200 Subject: [PATCH] doc: observer/reactive systems --- README.md | 17 +++--- TODO | 1 - docs/md/entity.md | 106 +++++++++++++++++++++++++++++++++-- src/entt/entity/observer.hpp | 2 +- 4 files changed, 111 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4ccbc6c38..daa672ef5 100644 --- a/README.md +++ b/README.md @@ -71,25 +71,24 @@ This project started off as a pure entity-component system. Over time the codebase has grown as more and more classes and functionalities were added.
Here is a brief, yet incomplete list of what it offers today: -* Statically generated integer **identifiers for types** (assigned either at +* Statically generated integer **identifiers** for types (assigned either at compile-time or at runtime). -* A `constexpr` utility for **human readable resource identifiers**. +* A `constexpr` utility for human readable **resource names**. * A minimal **configuration system** built using the monostate pattern. * An incredibly fast **entity-component system** based on sparse sets, with its own _pay for what you use_ policy to adjust performance and memory usage according to the users' requirements. * Views and groups to iterate entities and components and allow different access patterns, from **perfect SoA** to fully random. -* A lot of **facilities** built on top of the entity-component system to support - the users and avoid reinventing the wheel (dependencies, snapshot, actor class - for those who aren't confident with the architecture and so on). +* A lot of **facilities** built on top of the entity-component system to help + the users and avoid reinventing the wheel (dependencies, snapshot, actor + class, support for **reactive systems** and so on). * The smallest and most basic implementation of a **service locator** ever seen. -* A built-in, non-intrusive and macro-free **runtime reflection system**. +* A built-in, non-intrusive and macro-free runtime **reflection system**. * A **cooperative scheduler** for processes of any type. * All that is needed for **resource management** (cache, loaders, handles). -* **Delegates**, **signal handlers** (with built-in support for collectors) and - a tiny **event dispatcher** for immediate and delayed events to integrate in - loops. +* Delegates, **signal handlers** (with built-in support for collectors) and a + tiny event dispatcher for immediate and delayed events to integrate in loops. * A general purpose **event emitter** as a CRTP idiom based class template. * And **much more**! Check out the [**wiki**](https://github.com/skypjack/entt/wiki). diff --git a/TODO b/TODO index a621f5dfc..1e06ec6d1 100644 --- a/TODO +++ b/TODO @@ -24,4 +24,3 @@ * stable component handle that isn't affected by reallocations * multi component registry::remove and some others? * built-in support for dual (or N-) buffering -* reactive systems diff --git a/docs/md/entity.md b/docs/md/entity.md index 1af4f9541..2e61140a9 100644 --- a/docs/md/entity.md +++ b/docs/md/entity.md @@ -14,6 +14,7 @@ * [Vademecum](#vademecum) * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component) * [Observe changes](#observe-changes) + * [They call me Reactive System](#they-call-me-reactive-system) * [Runtime components](#runtime-components) * [A journey through a plugin](#a-journey-through-a-plugin) * [Sorting: is it possible?](#sorting-is-it-possible) @@ -57,8 +58,9 @@ used mostly in game development. ## A bitset-free entity-component system -`EnTT` is a _bitset-free_ entity-component system that doesn't require users to -specify the component set at compile-time.
+`EnTT` offers a _bitset-free_ entity-component system that doesn't require users +to specify the set of components neither at compile-time nor at runtime before +being able to use the library itself.
This is why users can instantiate the core class simply like: ```cpp @@ -71,6 +73,10 @@ In place of its more annoying and error-prone counterpart: entt::registry registry; ``` +Furthermore, there is no need to indicate to the library in any way that a type +of component exists and will be used sooner or later. When the time comes, users +can just use it and that's all. + ## Pay per use `EnTT` is entirely designed around the principle that users have to pay only for @@ -409,6 +415,90 @@ 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. +### They call me Reactive System + +As mentioned above, signals are the basic tools to construct reactive systems, +even if they are not enough on their own.
+`EnTT` tries to take another step in that direction with the `observer` class +template. + +In order to explain what reactive systems are, this is a slightly revised quote +from the documentation of the library that first introduced this tool, +[Entitas](https://github.com/sschmid/Entitas-CSharp): + +>Imagine you have 100 fighting units on the battlefield but only 10 of them +>changed their positions. Instead of using a normal system and updating all 100 +>entities depending on the position, you can use a reactive system which will +>only update the 10 changed units. So efficient. + +In `EnTT`, this means to iterating over a reduced set of entities and components +with respect to what would otherwise be returned from a view or group.
+On these words, however, the similarities with the proposal of Entitas also end. +The rules of language and the design of the library obviously impose and allow +different things. + +An `observer` is initialized with an instance of a registry and a set of rules +that describe what are the entities to intercept. As an example: + +```cpp +entt::observer observer{registry, entt::collector.replace()}; +``` + +The class also default constructible if required and it can be reconfigured at +any time by means of the `connect` member function. Moreover, instances can be +disconnected from the underlying registries through the `disconnect` member +function.
+The `observer` offers also some member functions to query its internal state and +to know if it's empty or how many entities it contains. Moreover, it can return +a raw pointer to the list of entities it contains.
+However, the most important features of this class are that: + +* It's iterable and therefore users can easily walk through the list of entities + by means of a range-for loop. +* It's clearable and therefore users can consume the entities and literally + reset the observer after each iteration. + +These aspects make the observer an incredibly powerful tool to know at any time +what are the entities that have started to respect the given rules since the +last time one asked: + +```cpp +for(const auto entity: observer) { + // ... +} + +observer.clear(); +``` + +The `collector` is an utility to use to generate a list of `matcher`s (the +actual rules) to use with an `observer`.
+There are two types of `matcher`s: + +* Observing matcher: an observer will return at least all the living entities + for which one or more of the given components have been explicitly replaced + and not yet destroyed. + + ```cpp + collector.replace(); + ``` + +* Grouping matcher: an observer will return at least all the living entities + that would have entered the given group if it existed and that would have + not yet left it. + + ```cpp + collector.group(entt::exclude); + ``` + + A grouping matcher supports also exclusion lists as well as single components. + +Roughly speaking, an observing matcher intercepts the entities for which the +given components are replaced (as in `registry::replace`) while a grouping +matcher tracks the entities that have assigned the given components since the +last time one asked.
+Note that, for a grouping matcher, if an entity already has all the components +except one and the missing type is assigned to it, it is intercepted. + ## Runtime components Defining components at runtime is useful to support plugin systems and mods in @@ -766,29 +856,37 @@ 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: +* To initialize an already existing entity, users 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.assign_or_replace(entity); ``` diff --git a/src/entt/entity/observer.hpp b/src/entt/entity/observer.hpp index 4443da961..50dced772 100644 --- a/src/entt/entity/observer.hpp +++ b/src/entt/entity/observer.hpp @@ -267,7 +267,7 @@ public: */ template void connect(basic_registry ®, basic_collector) { - release ? release(*target, *this) : void(); + disconnect(); connect(reg, std::make_index_sequence{}); target = ® view.reset();