Michele Caini 4 лет назад
Родитель
Сommit
d2d35d490a
2 измененных файлов с 100 добавлено и 172 удалено
  1. 1 0
      TODO
  2. 99 172
      docs/md/entity.md

+ 1 - 0
TODO

@@ -4,6 +4,7 @@
 * add examples (and credits) from @alanjfs :)
 
 WIP:
+* Making the most of range-destroy: disable this optimization, free pools make it possible to implement it on user side when needed
 * investigate: meta as_type_t<T> for getters and the like
 * investigate: add the possibility of disabling entities without deleting components thanks to the new full check
 * fast-contains for sparse sets (low prio but nice-to-have)

+ 99 - 172
docs/md/entity.md

@@ -72,8 +72,8 @@ used mostly in game development.
 
 ## Type-less and bitset-free
 
-`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.<br/>
+`EnTT` offers a sparse set based model that doesn't require users to specify the
+set of components neither at compile-time nor at runtime.<br/>
 This is why users can instantiate the core class simply like:
 
 ```cpp
@@ -91,20 +91,14 @@ When the time comes, just use it and that's all.
 
 ## Build your own
 
-`EnTT` is designed as a container that can be used at any time just as a vector
-or any other tool would be used. It doesn't attempt in any way to take over on
-the user code base, nor to control its main loop or process scheduling.<br/>
-Unlike other more or less known models, it makes use of independent pools. This
-has some advantages and disadvantages. The main purpose is to provide a fully
-customizable tool, where users have the freedom to define pools and opaque
-proxies for types with specific requirements.
-
-The library provides a default implementation for many things and a mixin model
-that allows users to completely replace or even just enrich the pool dedicated
-to one or more components.<br/>
-The built-in signal support is an example of that: defined as a mixin, it's
-easily disabled if not needed. Similarly, the storage class has a specialization
-that shows how everything is customizable down to the smallest detail.
+`EnTT` is designed as a container that can be used at any time, just like a
+vector or any other container. It doesn't attempt in any way to take over on the
+user codebase, nor to control its main loop or process scheduling.<br/>
+Unlike other more or less well known models, it also makes use of independent
+pools that can be extended via _static mixins_. The built-in signal support is
+an example of this flexible model: defined as a mixin, it's easily disabled if
+not needed. Similarly, the storage class has a specialization that shows how
+everything is customizable down to the smallest detail.
 
 ## Pay per use
 
@@ -117,8 +111,7 @@ Even worse, some approaches tend to heavily affect other functionalities like
 the construction and destruction of components to favor iterations, even when it
 isn't strictly required. In fact, slightly worse performance along non-critical
 paths are the right price to pay to reduce memory usage and have overall better
-perfomance sometimes and I've always wondered why this kind of tools do not
-leave me the choice.<br/>
+perfomance.<br/>
 `EnTT` follows a completely different approach. It gets the best out from the
 basic data structures and gives users the possibility to pay more for higher
 performance where needed.
@@ -143,47 +136,42 @@ have succeeded.
 
 The registry to store, the views and the groups to iterate. That's all.
 
-An entity (the _E_ of an _ECS_) is an opaque identifier that users should use
-as-is. Inspecting an identifier isn't recommended since its format can change in
-future and a registry has all the functionalities to query them out-of-the-box.
-The type `entt::entity` implements the concept of _entity identifier_.<br/>
-Components (the _C_ of an _ECS_) must be both move constructible and move
-assignable. They are list initialized by using the parameters provided to
-construct the component itself. No need to register components or their types
-neither with the registry nor with the entity-component system at all.<br/>
+The `entt::entity` type implements the concept of _entity identifier_. An entity
+(the _E_ of an _ECS_) is an opaque element to use as-is. Inspecting it isn't
+recommended since its format can change in future.<br/>
+Components (the _C_ of an _ECS_) are both move constructible and move assignable
+types. No need to register them nor their types.<br/>
 Systems (the _S_ of an _ECS_) can be plain functions, functors, lambdas and so
 on. It's not required to announce them in any case and have no requirements.
 
-The following sections explain in short how to use the entity-component system,
-the core part of the whole library.<br/>
-The project is composed of many other classes in addition to those describe
+The next sections go into detail on how to use the entity-component system part
+of the `EnTT` library.<br/>
+The project is composed of many other classes in addition to those described
 below. For more details, please refer to the inline documentation.
 
 # Pools
 
-In `EnTT`, pools of components are made available through a specialized version
-of a sparse set.
-
-Each pool contains all the instances of a single component and all the entities
-to which it's assigned. Sparse arrays are _paged_ to avoid wasting memory in
-some cases. Packed arrays of components are also paged to have pointer stability
-upon additions. Packed arrays of entities are not instead.<br/>
-All pools can rearrange their items in order to keep the internal arrays tightly
-packed and maximize performance.
+Pools of components are a sort of _specialized version_ of a sparse set. Each
+pool contains all the instances of a single component type and all the entities
+to which it's assigned.<br/>
+Sparse arrays are _paged_ to avoid wasting memory. Packed arrays of components
+are also paged to have pointer stability upon additions. Packed arrays of
+entities are not instead.<br/>
+All pools rearranges their items in order to keep the internal arrays tightly
+packed and maximize performance, unless pointer stability is enabled.
 
 # The Registry, the Entity and the Component
 
-A registry can store and manage entities, as well as create views and groups to
-iterate the underlying data structures.<br/>
+A registry stores and manages entities (or better, identifiers) and pools.<br/>
 The class template `basic_registry` lets users decide what's the preferred type
 to represent an entity. Because `std::uint32_t` is large enough for almost all
-the cases, there exists also the enum class `entt::entity` that _wraps_ it and
+the cases, there also exists the enum class `entt::entity` that _wraps_ it and
 the alias `entt::registry` for `entt::basic_registry<entt::entity>`.
 
 Entities are represented by _entity identifiers_. An entity identifier contains
 information about the entity itself and its version.<br/>
-User defined identifiers can be introduced through enum classes and class types
-that define an `entity_type` member of type `std::uint32_t` or `std::uint64_t`.
+User defined identifiers are allowed as enum classes and class types that define
+an `entity_type` member of type `std::uint32_t` or `std::uint64_t`.
 
 A registry is used both to construct and to destroy entities:
 
@@ -195,9 +183,9 @@ auto entity = registry.create();
 registry.destroy(entity);
 ```
 
-The `create` member function accepts also a hint and has an overload that gets
+The `create` member function also accepts a hint and has an overload that gets
 two iterators and can be used to generate multiple entities at once efficiently.
-Similarly, the `destroy` member function works also with a range of entities:
+Similarly, the `destroy` member function also works with a range of entities:
 
 ```cpp
 // destroys all the entities in a range
@@ -207,7 +195,7 @@ registry.destroy(view.begin(), view.end());
 
 In addition to offering an overload to force the version upon destruction. Note
 that this function removes all components from an entity before releasing its
-identifier. There exists also a _lighter_ alternative that only releases the
+identifier. There also exists a _lighter_ alternative that only releases the
 elements without poking in any pool, for use with orphaned entities:
 
 ```cpp
@@ -366,15 +354,12 @@ the component owned by an entity if any, a null pointer otherwise.
 
 ## Observe changes
 
-Because of how the registry works internally, it stores a bunch of signal
-handlers for each pool in order to notify some of its data structures on the
-construction and destruction of components or when an instance of a component is
-explicitly replaced by the user.<br/>
-These signal handlers are also exposed and made available to users. These are
-the basic bricks to build fancy things like dependencies and reactive systems.
+By default, each storage comes with a mixin that adds signal support to it.<br/>
+This allows for fancy things like dependencies and reactive systems.
 
-To get a sink to be used to connect and disconnect listeners so as to be
-notified on the creation of a component, use the `on_construct` member function:
+The `on_construct` member function returns a _sink_ (which is an object for
+connecting and disconnecting listeners) for those interested in notifications
+when a new instance of a given component type is created:
 
 ```cpp
 // connects a free function
@@ -390,15 +375,12 @@ registry.on_construct<position>().disconnect<&my_free_function>();
 registry.on_construct<position>().disconnect<&my_class::member>(instance);
 ```
 
-To be notified when a component is destroyed, use the `on_destroy` member
-function instead. Finally, the `on_update` member function will return a sink
-to which to connect listeners to observe changes.<br/>
-In the last case, given the way C++ works, it's also necessary to use specific
-member functions to allow the signal to be triggered. In particular, listeners
-attached to `on_update` will only be invoked following a call to `replace` or
-`patch`.
+Similarly, `on_destroy` and `on_update` are used to receive notifications about
+the destruction and update of an instance, respectively.<br/>
+Because of how C++ works, listeners attached to `on_update` are only invoked
+following a call to `replace`, `emplace_or_replace` or `patch`.
 
-The function type of a listener should be equivalent to the following:
+The function type of a listener is equivalent to the following:
 
 ```cpp
 void(entt::registry &, entt::entity);
@@ -432,17 +414,8 @@ There are also some limitations on what a listener can and cannot do:
   intended to provide users with an easy way to perform cleanup and nothing
   more.
 
-To a certain extent, these limitations don't apply. However, it's risky to try
-to force them and users should respect the limitations unless they know exactly
-what they are doing.
-
-Events and therefore listeners must not be used as replacements for systems.
-They shouldn't contain much logic and interactions with a registry should be
-kept to a minimum. Moreover, the greater the number of listeners, the greater
-the performance hit when components are created or destroyed.
-
-Please, refer to the documentation of the signal class to know all the features
-it offers.<br/>
+Please, refer to the documentation of the signal class to know about all the
+features it offers.<br/>
 There are many useful but less known functionalities that aren't described here,
 such as the connection objects or the possibility to attach listeners with a
 list of parameters that is shorter than that of the signal itself.
@@ -566,11 +539,10 @@ one.
 
 ## Sorting: is it possible?
 
-Sorting entities and components is possible with `EnTT`. In particular, it's
-feasible with an in-place algorithm that doesn't require memory allocations nor
-anything else and is therefore particularly convenient.<br/>
-With this in mind, there are two functions that respond to slightly different
-needs:
+Sorting entities and components is possible with `EnTT`. In particular, it uses
+an in-place algorithm that doesn't require memory allocations nor anything else
+and is therefore particularly convenient.<br/>
+There are two functions that respond to slightly different needs:
 
 * Components can be sorted either directly:
 
@@ -607,12 +579,11 @@ components. Refer to the specific documentation for more details.
 ## Helpers
 
 The so called _helpers_ are small classes and functions mainly designed to offer
-built-in support for the most basic functionalities.<br/>
-The list of helpers will grow longer as time passes and new ideas come out.
+built-in support for the most basic functionalities.
 
 ### Null entity
 
-In `EnTT`, the `entt::null` variable models the concept of _null entity_.<br/>
+The `entt::null` variable models the concept of _null entity_.<br/>
 The library guarantees that the following expression always returns false:
 
 ```cpp
@@ -646,11 +617,11 @@ used to create a null entity.
 
 ### Tombstone
 
-In addition to the null entity, `EnTT` also models the concept of _tombstone_
-with the `entt::tombstone` variable.<br/>
+Similar to the null entity, the `entt::tombstone` variable models the concept of
+_tombstone_.<br/>
 Once created, the integral form of the two values is the same, although they
-affect different parts of an identifier. In fact, the tombstone uses only the
-version part and is completely transparent to the entity part.
+affect different parts of an identifier. In fact, the tombstone only uses the
+version part of it and is completely transparent to the entity part.
 
 Also in this case, the following expression always returns false:
 
@@ -727,9 +698,9 @@ purpose.
 
 ### Invoke
 
-Sometimes it's useful to be able to directly invoke a member function of a
-component as a callback. It's already possible in practice but requires users to
-_extend_ their classes and this may not always be possible.<br/>
+Sometimes it's useful to directly invoke a member function of a component as a
+callback. It's already possible in practice but requires users to _extend_ their
+classes and this may not always be possible.<br/>
 The `invoke` helper allows to _propagate_ the signal in these cases:
 
 ```cpp
@@ -777,17 +748,13 @@ users might want to consider using handles, either const or non-const.
 
 ### Organizer
 
-The `organizer` class template offers minimal support (but sufficient in many
-cases) for creating an execution graph from functions and their requirements on
-resources.<br/>
+The `organizer` class template offers support for creating an execution graph
+from a set of functions and their requirements on resources.<br/>
 The resulting tasks aren't executed in any case. This isn't the goal of this
 tool. Instead, they are returned to the user in the form of a graph that allows
 for safe execution.
 
-The functions are added in order of execution to the organizer. Free functions
-and member functions are supported as template parameters, however there is also
-the possibility to pass pointers to free functions or decayed lambdas as
-parameters to the `emplace` member function:
+All functions are added in order of execution to the organizer:
 
 ```cpp
 entt::organizer organizer;
@@ -803,23 +770,15 @@ organizer.emplace<&clazz::member_function>(&instance);
 organizer.emplace(+[](const void *, entt::registry &) { /* ... */ });
 ```
 
-As for free functions and member functions, these are the parameters that can be
-presented by their function types and that will be correctly handled:
+These are the parameters that a free function or a member function can accept:
 
-* A possibly constant reference to a registry. The one passed to the task when
-  it's run will also be passed to the function as-is.
-
-* An `entt::view` with any possible combination of types. It will be created
-  from the registry passed to the task and supplied directly to the function.
-
-* A possibly constant reference to any type `T`. It will be interpreted as
-  context variable, which will be created within the registry and passed to the
-  function.
+* A possibly constant reference to a registry.
+* An `entt::basic_view` with any possible combination of types.
+* A possibly constant reference to any type `T` (that is, a context variable)
 
 The function type for free functions and decayed lambdas passed as parameters to
-`emplace` is `void(const void *, entt::registry &)` instead. The registry is the
-same as provided to the task. The first parameter is an optional pointer to user
-defined data to provide upon registration:
+`emplace` is `void(const void *, entt::registry &)` instead. The first parameter
+is an optional pointer to user defined data to provide upon registration:
 
 ```cpp
 clazz instance;
@@ -914,7 +873,7 @@ use the preferred tool.
 Each registry has a _context_ associated with it, which is an `any` object map
 accessible by type for convenience.<br/>
 The context is returned via the `ctx` functions and offers a minimal set of
-features including the following:
+feature including the following:
 
 ```cpp
 // creates a new context variable initialized with the given values
@@ -987,25 +946,16 @@ this reason, a _stable_ deletion method is also offered. This one is such that
 the position of the elements is preserved by creating tombstones upon deletion
 rather than trying to fill the holes that are created.
 
-For performance reasons, `EnTT` will also favor storage compaction in all cases,
-although often accessing a component occurs mostly randomly or traversing pools
-in a non-linear order on the user side (as in the case of a hierarchy).<br/>
+For performance reasons, `EnTT` favors storage compaction in all cases, although
+often accessing a component occurs mostly randomly or traversing pools in a
+non-linear order on the user side (as in the case of a hierarchy).<br/>
 In other words, pointer stability is not automatic but is enabled on request.
 
 ### In-place delete
 
-By default, `EnTT` keeps all pools compact when a component is removed. This is
-done through a swap-and-pop between the removed item and the one occupying the
-last position in the storage.<br/>
-Unfortunately, this also inevitably leads the components to change position
-within the storage, making direct access almost impossible (be it via pointer or
-index).
-
-However, the underlying model with its independent pools helps introduce storage
-with different deletion policies. In particular, the library offers out of the
-box support for in-place deletion, thus offering storage with completely stable
-pointers.<br/>
-This is done by specializing the `component_traits` class. The compile-time
+The library offers out of the box support for in-place deletion, thus offering
+storage with completely stable pointers.<br/>
+This is achieved by specializing the `component_traits` class. The compile-time
 definition common to all components is the following:
 
 ```cpp
@@ -1032,11 +982,10 @@ struct entt::component_traits<position>: basic_component_traits {
 This will ensure in-place deletion for the `position` component without further
 user intervention.<br/>
 Views and groups adapt accordingly when they detect a storage with a different
-deletion policy than the default. No specific action is required from the user
-once in-place deletion is enabled. In particular:
+deletion policy than the default. In particular:
 
 * Groups are incompatible with stable storage and will even refuse to compile.
-* Multi type views are completely transparent to storage policies.
+* Multi type and runtime views are completely transparent to storage policies.
 * Single type views for stable storage types offer the same interface of multi
   type views. For example, only `size_hint` is available.
 
@@ -1084,12 +1033,7 @@ struct entt::component_traits<transform>: basic_component_traits {
 Furthermore, it's quite common for a group of elements to be created close in
 time and therefore fallback into adjacent positions, thus favoring locality even
 on random accesses. Locality that won't be sacrificed over time given the
-stability of storage positions, with undoubted performance advantages.<br/>
-Of course, the cost moves to linear iterations, where views and groups will have
-to identify (and discard) all tombstones. However, once considered the benefits,
-from performance to ease of use, and given the many optimizations that make this
-cost negligible, this is configured as one of the most convenient solutions and
-certainly something to take into consideration.
+stability of storage positions, with undoubted performance advantages.
 
 ## Making the most of range-destroy
 
@@ -1133,16 +1077,11 @@ out of it.
 
 ## Meet the runtime
 
-`EnTT` takes full advantage of what the language offers at compile-time.<br/>
-However, this can have its downsides (well known to those familiar with type
-erasure techniques).
-
-To bridge this gap, the library provides a bunch of utilities and features that
-can be very useful when needed.<br/>
-On the one hand, storage classes are standalone objects that allow users to work
-with components within certain limits and without knowing the actual types. On
-the other hand, it's possible to create component storage dynamically and link
-them to a name rather than a type.
+`EnTT` takes advantage of what the language offers at compile-time. However,
+this can have its downsides (well known to those familiar with type erasure
+techniques).<br/>
+To fill the gap, the library also provides a bunch of utilities and feature that
+can be very useful to handle types and pools at runtime.
 
 ### A base class to rule them all
 
@@ -1150,7 +1089,7 @@ Storage classes are fully self-contained types. These can be extended via mixins
 to add more functionalities (generic or type specific). In addition, they offer
 a basic set of functions that already allow users to go very far.<br/>
 The aim is to limit the need for customizations as much as possible, offering
-what is usually necessary for the majority of cases.
+what is usually necessary for the vast majority of cases.
 
 When a storage is used through its base class (i.e. when its actual type isn't
 known), there is always the possibility of receiving a `type_info` describing
@@ -1219,8 +1158,8 @@ However, this doesn't work well for users who want to create multiple storage of
 the same type associated with different _names_, such as for interacting with a
 scripting system.
 
-Nowadays, the library has _solved_ this limitation and offers the possibility of
-associating a _type_ with a name (or rather, a numeric identifier):
+Nowadays, the library has solved this problem and offers the possibility of
+associating a type with a _name_ (or rather, a numeric identifier):
 
 ```cpp
 using namespace entt::literals;
@@ -1251,7 +1190,7 @@ _reach_ manually created ones:
 registry.destroy(entity);
 ```
 
-Finally, a storage of this type can be used with any view (which also accept
+Finally, a storage of this type can be used with any view (which also accepts
 multiple storages of the same type, if necessary):
 
 ```cpp
@@ -2108,9 +2047,8 @@ is further reduced in most cases.
 
 ### More performance, more constraints
 
-Groups are a (much) faster alternative to views. However, the higher the
-performance, the greater the constraints on what is allowed and what is
-not.<br/>
+Groups are a faster alternative to views. However, the higher the performance,
+the greater the constraints on what is allowed and what is not.<br/>
 In particular, groups add in some rare cases a limitation on the creation of
 components during iterations. It happens in quite particular cases. Given the
 nature and the scope of the groups, it isn't something in which it will happen
@@ -2155,33 +2093,22 @@ the same types for which _empty base optimization_ (EBO) is possibile.<br/>
 performance and memory usage. However, this also has consequences that are worth
 mentioning.
 
-When an empty type is detected, it's not instantiated in any case. Therefore,
-only the entities to which it's assigned are made available.<br/>
-There doesn't exist a way to _get_ empty types from a registry, views and groups
-will never return instances for them (for example, during a call to `each`) and
-some functions such as `try_get` or the raw access to the list of components
-aren't available for empty types. Finally, the `sort` functionality will only
-accepts callbacks that require to return entities rather than components:
-
-```cpp
-registry.sort<empty_type>([](const entt::entity lhs, const entt::entity rhs) {
-    return entt::registry::entity(lhs) < entt::registry::entity(rhs);
-});
-```
-
+When an empty type is detected, it's not instantiated by default. Therefore,
+only the entities to which it's assigned are made available. There doesn't exist
+a way to _get_ empty types from a registry. Views and groups will never return
+their instances (for example, during a call to `each`).<br/>
 On the other hand, iterations are faster because only the entities to which the
 type is assigned are considered. Moreover, less memory is used, mainly because
 there doesn't exist any instance of the component, no matter how many entities
 it is assigned to.
 
-More in general, none of the features offered by the library is affected, but
-for the ones that require to return actual instances.<br/>
+More in general, none of the feature offered by the library is affected, but for
+the ones that require to return actual instances.<br/>
 This optimization can be disabled for the whole application by defining the
 `ENTT_NO_ETO` macro. In this case, empty types will be treated like all other
-types, no matter what.<br/>
-Otherwise, users can specialize the `component_traits` template class and in
-particular the `ignore_if_empty` alias, disabling this optimization for some
-types only.
+types. Otherwise, users can also specialize the `component_traits` template
+class and in particular the `ignore_if_empty` alias, disabling this optimization
+for some types only.
 
 # Multithreading
 
@@ -2280,7 +2207,7 @@ generated from a const registry.
 
 Fortunately, there is also a way to instantiate storage classes early when in
 doubt or when there are special requirements.<br/>
-Calling the `prepare` method is equivalent to _announcing_ the existence of a
+Calling the `storage` method is equivalent to _announcing_ the existence of a
 particular storage, to avoid running into problems. For those interested, there
 are also alternative approaches, such as a single threaded tick for the registry
 warm-up, but these are not always applicable.<br/>