Просмотр исходного кода

registry: deprecate ::clone/::stamp

Michele Caini 6 лет назад
Родитель
Сommit
6a46325e7e
3 измененных файлов с 125 добавлено и 26 удалено
  1. 1 1
      TODO
  2. 122 25
      docs/md/entity.md
  3. 2 0
      src/entt/entity/registry.hpp

+ 1 - 1
TODO

@@ -20,7 +20,7 @@
 * remove copy-ctor/op from sparse set and storage (it doesn't make much sense)
 
 * WIP:
- - deprecate clone, stamp, snapshot, loader, ...
+ - deprecate snapshot, loader, ...
  - provide documentation to describe alternatives
 
 * WIP: snapshot rework/deprecation

+ 122 - 25
docs/md/entity.md

@@ -18,11 +18,12 @@
   * [Sorting: is it possible?](#sorting-is-it-possible)
   * [Helpers](#helpers)
     * [Null entity](#null-entity)
-    * [Stamp](#stamp)
     * [Dependencies](#dependencies)
     * [Actor](#actor)
     * [Context variables](#context-variables)
   * [Meet the runtime](#meet-the-runtime)
+    * [Cloning a registry](#cloning-a-registry)
+    * [Stamping an entity](#stamping-an-entity)
   * [Snapshot: complete vs continuous](#snapshot-complete-vs-continuous)
     * [Snapshot loader](#snapshot-loader)
     * [Continuous loader](#continuous-loader)
@@ -576,26 +577,6 @@ initialized entity isn't the same as `entt::null`. Therefore, although
 `entt::entity{}` is in some sense an alias for entity 0, none of them can be
 used to create a null entity.
 
-### Stamp
-
-Using multiple registries at the same time is quite common. Examples are the
-separation of the UI from the simulation or the loading of different scenes in
-the background, possibly on a separate thread, without having to keep track of
-which entity belongs to which scene.<br/>
-In fact, with `EnTT` this is even a recommended practice, as the registry is
-nothing more than an opaque container you can swap at any time.
-
-Once there are multiple registries available, one or more methods are needed to
-transfer information from one container to another. This results in the `stamp`
-member functions of the `registry` class.<br/>
-This function takes one entity from a registry and uses it to _stamp_ one or
-more entities in another registry (or even the same, making local copies).
-
-It opens definitely the doors to a lot of interesting features like migrating
-entities between registries, prototypes, shadow registry, prefabs, shared
-components without explicit ownership models and copy-on-write policies among
-the other things.
-
 ### Dependencies
 
 The `registry` class is designed to be able to create short circuits between its
@@ -680,10 +661,10 @@ otherwise it returns a null pointer.
 ## Meet the runtime
 
 Type identifiers are stable in `EnTT` during executions and most of the times
-also across different executions. This makes them suitable to mix runtime and
-compile-time features.<br/>
-The registry offers a couple of functions to _visit_ it and get the types of
-components it manages:
+also across different executions and across boundaries. This makes them suitable
+to mix runtime and compile-time features.<br/>
+The registry offers a function to _visit_ it and get the types of components it
+manages:
 
 ```cpp
 registry.visit([](const auto component) {
@@ -703,6 +684,122 @@ This helps to create a bridge between the registry, that is heavily based on the
 C++ type system, and any other context where the compile-time isn't an option.
 For example: plugin systems, meta system, serialization, and so on.
 
+### Cloning a registry
+
+Cloning a registry isn't a suggested practice since it could trigger many copies
+and cut down the performance. Moreover, because of how the `registry` class is
+designed, supporting this as a built-in feature would increase the compilation
+times also for the users that aren't interested in cloning. Even worse, it would
+make difficult to define different _cloning policies_ for different types when
+required.<br/>
+This is why function definitions for cloning have been moved to the user space.
+The `visit` member function of the `registry` class can help filling the gap,
+along with the range-`assign` functionality.
+
+A general purpose cloning function could be defined as:
+
+```cpp
+template<typename Type>
+void clone(const entt::registry &from, entt::registry &to) {
+    to.assign<Type>(from.data<Type>(), from.data<Type>() + from.size<Type>(), from.raw<Type>());
+}
+```
+
+This is probably the fastest method to inject entities and components in a
+registry that isn't necessarily empty. All new elements are _appended_ to the
+existing ones, if any.<br/>
+This function is also eligible for type erasure in order to create a mapping
+between type identifiers and opaque methods for cloning:
+
+```cpp
+using clone_fn_type = void(const entt::registry &, entt::registry &);
+std::unordered_map<ENTT_ID_TYPE, clone_fn_type *> clone_functions;
+
+// ...
+
+clone_functions[entt::type_info<position>::id()] = &clone<position>;
+clone_functions[entt::type_info<velocity>::id()] = &clone<velocity>;
+```
+
+Stamping a registry becomes straightforward with such a mapping then:
+
+```cpp
+entt::registry from;
+entt::registry to;
+
+// ...
+
+from.visit([this, &to](const auto type_id) {
+    clone_functions[type_id](from, to);
+});
+```
+
+Custom cloning functions are also pretty easy to define. Moreover, also cloning
+registries specialized with different identifiers is possible this way.<br/>
+As a side note, cloning functions could be also attached to a reflection system
+where meta types are resolved using the runtime type identifiers.
+
+### Stamping an entity
+
+Using multiple registries at the same time is quite common. Examples are the
+separation of the UI from the simulation or the loading of different scenes in
+the background, possibly on a separate thread, without having to keep track of
+which entity belongs to which scene.<br/>
+In fact, with `EnTT` this is even a recommended practice, as the registry is
+nothing more than an opaque container you can swap at any time.
+
+Once there are multiple registries available, one or more methods are needed to
+transfer information from one container to another though.<br/>
+This is where the `visit` member function of the `registry` class enters the
+game.
+
+Since stamping a component could require different methods for different types
+and not all users want to benefit from this feature, function definitions have
+been moved from the registry to the user space.<br/>
+This helped to reduce compilation times and to allow for maximum flexibility,
+even though it requires users to set up their own stamping functions.
+
+The best bet here is probably to define a reflection system or a mapping between
+the type identifiers and their opaque functions for stamping. As an example:
+
+```
+template<typename Type>
+void stamp(const entt::registry &from, const entt::entity src, entt::registry &to, const entt::entity dst) {
+    to.assign_or_replace<Type>(dst, from.get_or_assign<Type>(src));
+}
+```
+
+If the definition above is treated as a general purpose function for stamping,
+one can easily construct a map like the following one as a data member of a
+dedicate system:
+
+```cpp
+using stamp_fn_type = void(const entt::registry &, const entt::entity, entt::registry &, const entt::entity);
+std::unordered_map<ENTT_ID_TYPE, stamp_fn_type *> stamp_functions;
+
+// ...
+
+stamp_functions[entt::type_info<position>::id()] = &stamp<position>;
+stamp_functions[entt::type_info<velocity>::id()] = &stamp<velocity>;
+```
+
+Then _stamp_ entities across different registries as:
+
+```cpp
+entt::registry from;
+entt::registry to;
+
+// ...
+
+from.visit(src, [this, &to, dst](const auto type_id) {
+    stamp_functions[type_id](from, src, to, dst);
+});
+```
+
+This way it's also pretty easy to define custom stamping functions for _special_
+types if needed. Moreover, stamping entities across registries specialized with
+different identifiers is possibile in practice.
+
 ## Snapshot: complete vs continuous
 
 The `registry` class offers basic support to serialization.<br/>

+ 2 - 0
src/entt/entity/registry.hpp

@@ -1535,6 +1535,7 @@ public:
      * @return A fresh copy of the registry.
      */
     template<typename... Component, typename... Exclude>
+    [[deprecated("use ::visit and custom (eventually erased) functions instead")]]
     basic_registry clone(exclude_t<Exclude...> = {}) const {
         basic_registry other;
 
@@ -1580,6 +1581,7 @@ public:
      * @param src A valid entity identifier to be copied.
      */
     template<typename... Exclude>
+    [[deprecated("use ::visit and custom (eventually erased) functions instead")]]
     void stamp(const entity_type dst, const basic_registry &other, const entity_type src, exclude_t<Exclude...> = {}) {
         std::for_each(other.pools.cbegin(), other.pools.cend(), [this, dst, src](auto &&pdata) {
             if(((pdata.type_id != type_info<Exclude>::id()) && ...) && pdata.pool->has(src)) {