Explorar el Código

registry: stomp and spawn

Michele Caini hace 6 años
padre
commit
83f9d42d31
Se han modificado 5 ficheros con 260 adiciones y 98 borrados
  1. 3 2
      TODO
  2. 30 15
      docs/md/entity.md
  3. 96 39
      src/entt/entity/registry.hpp
  4. 2 4
      src/entt/entity/storage.hpp
  5. 129 38
      test/entt/entity/registry.cpp

+ 3 - 2
TODO

@@ -28,5 +28,6 @@ TODO
 * make meta work across boundaries
   - inline variables are fine here, only the head represents a problem
   - we should always resolve by looking into the list of types when working across boundaries, no direct resolve
-* review stomp signature: first, last, other, from <-- the registry "acquires" the other entity
-* create from prototype (literally a stomp with batch creation)
+* nested groups: AB/ABC/ABCD/... (hints: sort, check functions)
+* use make_tuple and reference_wrapper instead of explicing tuple<T &>
+* improve multi-stomp so as to reuse the known pool and the known element

+ 30 - 15
docs/md/entity.md

@@ -20,7 +20,7 @@
   * [Sorting: is it possible?](#sorting-is-it-possible)
   * [Helpers](#helpers)
     * [Null entity](#null-entity)
-    * [Multiple registries](#multiple-registries)
+    * [Stomp and spawn](#stomp-and-spawn)
     * [Dependencies](#dependencies)
     * [Tags](#tags)
     * [Actor](#actor)
@@ -44,6 +44,7 @@
 * [Empty type optimization](#empty-type-optimization)
 * [Multithreading](#multithreading)
   * [Iterators](#iterators)
+* [Beyond this document](#beyond-this-document)
 <!--
 @endcond TURN_OFF_DOXYGEN
 -->
@@ -666,26 +667,29 @@ const auto entity = registry.create();
 const bool null = (entity == entt::null);
 ```
 
-### Multiple registries
+### Stomp and spawn
 
 The use of multiple registries is quite common. Examples of use are the
 separation of the UI from the simulation or the loading of different scenes in
 the background, possibly on a separate thread, without having to keep track of
 which entity belongs to which scene.<br/>
 In fact, with `EnTT` this is even a recommended practice, as the registry is
-nothing more than a container and different optimizations can be applied to
-different containers.
-
-Once there are multiple registries available, however, a method is needed to
-transfer information from one container to another and this results in the
-`stomp` member function of the `registry` class.<br/>
-This function allows to take one or more entities from a registry and use them
-to _stomp_ other entities in another registry (or even the same, actually making
-local copies).<br/>
-It opens definitely the doors to a lot of interesting features like migrating
-entities between registries, prototypes, shadow registry, prefabs, shared
-components without an explicit owner and copy-on-write policies among the other
-things.
+nothing more than a container and different optimizations and strategies can be
+applied to different containers.
+
+Once there are multiple registries available, however, one or more methods are
+needed to transfer information from one container to another and this results in
+the `stomp` member function and a couple of overloads of the `create` member
+function for the `registry` class .<br/>
+The `stomp` function allows to take one entity from a registry and use it to
+_stomp_ one or more entities in another registry (or even the same, actually
+making local copies). On the other hand, the overloads of the `create` member
+function can be used to spawn new entities from a prototype.
+
+These features open definitely the doors to a lot of interesting features like
+migrating entities between registries, prototypes, shadow registry, prefabs,
+shared components without an explicit owner and copy-on-write policies among the
+other things.
 
 ### Dependencies
 
@@ -1672,3 +1676,14 @@ This may change in the future and the iterators will almost certainly return
 both the entities and a list of references to their components sooner or later.
 Multi-pass guarantee won't break in any case and the performance should even
 benefit from it further.
+
+# Beyond this document
+
+There are many other features and functions not listed in this document.<br/>
+`EnTT` and in particular its ECS part is in continuous development and some
+things could be forgotten, others could have been omitted on purpose to reduce
+the size of this file. Unfortunately, some parts may even be outdated and still
+to be updated.
+
+For further information, it's recommended to refer to the documentation included
+in the code itself or join the official channels to ask a question.

+ 96 - 39
src/entt/entity/registry.hpp

@@ -85,9 +85,9 @@ class basic_registry {
             }
         }
 
-        template<typename It>
-        auto batch(basic_registry &registry, It first, It last) {
-            auto it = storage<Entity, Component>::batch(first, last);
+        template<typename It, typename... Comp>
+        auto batch(basic_registry &registry, It first, It last, const Comp &... value) {
+            auto it = storage<Entity, Component>::batch(first, last, value...);
 
             if(!construction.empty()) {
                 std::for_each(first, last, [this, &registry, it](const auto entt) mutable {
@@ -543,11 +543,11 @@ public:
      * Users should not care about the type of the returned entity identifier.
      * In case entity identifers are stored around, the `valid` member
      * function can be used to know if they are still valid or the entity has
-     * been destroyed and potentially recycled.
+     * been destroyed and potentially recycled.<br/>
+     * The returned entity has assigned the given components, if any.
      *
-     * The returned entity has assigned the given components, if any. The
-     * components must be at least default constructible. A compilation error
-     * will occur otherwhise.
+     * The components must be at least default constructible. A compilation
+     * error will occur otherwhise.
      *
      * @tparam Component Types of components to assign to the entity.
      * @return A valid entity identifier if the component list is empty, a tuple
@@ -556,24 +556,14 @@ public:
      */
     template<typename... Component>
     auto create() {
-        entity_type entity;
-
-        if(destroyed == null) {
-            entity = entities.emplace_back(entity_type(entities.size()));
-            // traits_type::entity_mask is reserved to allow for null identifiers
-            ENTT_ASSERT(to_integer(entity) < traits_type::entity_mask);
-        } else {
-            const auto entt = to_integer(destroyed);
-            const auto version = to_integer(entities[entt]) & (traits_type::version_mask << traits_type::entity_shift);
-            destroyed = entity_type{to_integer(entities[entt]) & traits_type::entity_mask};
-            entity = entity_type{entt | version};
-            entities[entt] = entity;
-        }
+        entity_type entities[1]{};
 
         if constexpr(sizeof...(Component) == 0) {
-            return entity;
+            create<Component...>(std::begin(entities), std::end(entities));
+            return entities[0];
         } else {
-            return std::tuple<entity_type, decltype(assign<Component>(entity))...>{entity, assign<Component>(entity)...};
+            auto it = create<Component...>(std::begin(entities), std::end(entities));
+            return std::tuple<entity_type, decltype(assign<Component>(entities[0]))...>{entities[0], *std::get<typename pool_type<Component>::iterator_type>(it)...};
         }
     }
 
@@ -582,6 +572,9 @@ public:
      *
      * @sa create
      *
+     * The components must be at least move and default insertable. A
+     * compilation error will occur otherwhise.
+     *
      * @tparam Component Types of components to assign to the entity.
      * @tparam It Type of forward iterator.
      * @param first An iterator to the first element of the range to generate.
@@ -597,13 +590,16 @@ public:
         std::generate(first, last, [this]() {
             entity_type curr;
 
-            if(destroyed != null) {
+            if(destroyed == null) {
+                curr = entities.emplace_back(entity_type(entities.size()));
+                // traits_type::entity_mask is reserved to allow for null identifiers
+                ENTT_ASSERT(to_integer(curr) < traits_type::entity_mask);
+            } else {
                 const auto entt = to_integer(destroyed);
                 const auto version = to_integer(entities[entt]) & (traits_type::version_mask << traits_type::entity_shift);
                 destroyed = entity_type{to_integer(entities[entt]) & traits_type::entity_mask};
-                curr = (entities[entt] = entity_type{entt | version});
-            } else {
-                curr = entities.emplace_back(entity_type(entities.size()));
+                curr = entity_type{entt | version};
+                entities[entt] = curr;
             }
 
             return curr;
@@ -614,6 +610,48 @@ public:
         }
     }
 
+    /**
+     * @brief Creates a new entity from a prototype entity.
+     *
+     * @sa create
+     *
+     * @tparam Component Types of components to copy.
+     * @tparam Exclude Types of components not to be copied.
+     * @param src A valid entity identifier to be copied.
+     * @param other The registry that owns the source entity.
+     * @return A valid entity identifier.
+     */
+    template<typename... Component, typename... Exclude>
+    auto create(entity_type src, basic_registry &other, exclude_t<Exclude...> = {}) {
+        entity_type entities[1]{};
+        create<Component...>(std::begin(entities), std::end(entities), src, other, exclude<Exclude...>);
+        return entities[0];
+    }
+
+    /**
+     * @brief Assigns each element in a range an entity from a prototype entity.
+     *
+     * @sa create
+     *
+     * @tparam Component Types of components to copy.
+     * @tparam Exclude Types of components not to be copied.
+     * @param first An iterator to the first element of the range to generate.
+     * @param last An iterator past the last element of the range to generate.
+     * @param src A valid entity identifier to be copied.
+     * @param other The registry that owns the source entity.
+     */
+    template<typename... Component, typename It, typename... Exclude>
+    void create(It first, It last, entity_type src, basic_registry &other, exclude_t<Exclude...> = {}) {
+        create(first, last);
+
+        if constexpr(sizeof...(Component) == 0) {
+            stomp<Component...>(first, last, src, other, exclude<Exclude...>);
+        } else {
+            static_assert(sizeof...(Component) == 0 || sizeof...(Exclude) == 0);
+            (assure<Component>()->batch(*this, first, last, other.get<Component>(src)), ...);
+        }
+    }
+
     /**
      * @brief Destroys an entity and lets the registry recycle the identifier.
      *
@@ -1528,16 +1566,15 @@ public:
     }
 
     /**
-     * @brief Makes a full or partial copy of an entity.
+     * @brief Stomps an entity and its components.
      *
      * The components must be copyable for obvious reasons. The entities
      * must be both valid.<br/>
      * If no components are provided, the registry will try to copy all the
      * existing types. The non-copyable ones will be ignored.
      *
-     * This feature supports exclusion lists. The excluded types have higher
-     * priority than those indicated for copying. An excluded type will never be
-     * copied.
+     * This feature supports exclusion lists as an alternative to component
+     * lists. An excluded type will never be copied.
      *
      * @warning
      * Attempting to copy components that aren't copyable results in unexpected
@@ -1554,25 +1591,45 @@ public:
      *
      * @tparam Component Types of components to copy.
      * @tparam Exclude Types of components not to be copied.
-     * @param from A valid entity identifier to be copied.
-     * @param other The registry that owns the target entity.
-     * @param to A valid entity identifier to copy to.
+     * @param dst A valid entity identifier to copy to.
+     * @param src A valid entity identifier to be copied.
+     * @param other The registry that owns the source entity.
      */
     template<typename... Component, typename... Exclude>
-    void stomp(const Entity from, basic_registry &other, const Entity to, exclude_t<Exclude...> = {}) {
+    void stomp(const entity_type dst, const entity_type src, basic_registry &other, exclude_t<Exclude...> = {}) {
+        const entity_type entities[1]{dst};
+        stomp<Component...>(std::begin(entities), std::end(entities), src, other, exclude<Exclude...>);
+    }
+
+    /**
+     * @brief Stomps the entities in a range and their components.
+     *
+     * @sa stomp
+     *
+     * @tparam Component Types of components to copy.
+     * @tparam Exclude Types of components not to be copied.
+     * @param first An iterator to the first element of the range to stomp.
+     * @param last An iterator past the last element of the range to stomp.
+     * @param src A valid entity identifier to be copied.
+     * @param other The registry that owns the source entity.
+     */
+    template<typename... Component, typename It, typename... Exclude>
+    void stomp(It first, It last, const entity_type src, basic_registry &other, exclude_t<Exclude...> = {}) {
+        static_assert(sizeof...(Component) == 0 || sizeof...(Exclude) == 0);
         static_assert(std::conjunction_v<std::is_copy_constructible<Component>...>);
-        ENTT_ASSERT(valid(from) && other.valid(to));
 
-        for(auto pos = pools.size(); pos; --pos) {
-            const auto &pdata = pools[pos-1];
+        for(auto pos = other.pools.size(); pos; --pos) {
+            const auto &pdata = other.pools[pos-1];
             ENTT_ASSERT(!sizeof...(Component) || !pdata.pool || pdata.stomp);
 
             if(pdata.pool && pdata.stomp
                     && (!sizeof...(Component) || ... || (pdata.runtime_type == to_integer(type<Component>())))
                     && ((pdata.runtime_type != to_integer(type<Exclude>())) && ...)
-                    && pdata.pool->has(from))
+                    && pdata.pool->has(src))
             {
-                pdata.stomp(*pdata.pool, from, other, to);
+                std::for_each(first, last, [this, &pdata, src](const auto entity) {
+                    pdata.stomp(*pdata.pool, src, *this, entity);
+                });
             }
         }
     }

+ 2 - 4
src/entt/entity/storage.hpp

@@ -351,8 +351,7 @@ public:
      */
     template<typename It>
     iterator_type batch(It first, It last) {
-        const auto length = last - first;
-        instances.resize(instances.size() + length);
+        instances.resize(instances.size() + std::distance(first, last));
         // entity goes after component in case constructor throws
         underlying_type::batch(first, last);
         return begin();
@@ -375,8 +374,7 @@ public:
      */
     template<typename It>
     iterator_type batch(It first, It last, const object_type &value) {
-        const auto length = last - first;
-        instances.resize(instances.size() + length, value);
+        instances.resize(instances.size() + std::distance(first, last), value);
         // entity goes after component in case constructor throws
         underlying_type::batch(first, last);
         return begin();

+ 129 - 38
test/entt/entity/registry.cpp

@@ -1187,6 +1187,82 @@ TEST(Registry, CreateManyEntitiesWithComponentsAtOnceWithListener) {
     ASSERT_EQ(listener.counter, 6);
 }
 
+TEST(Registry, CreateFromPrototype) {
+    entt::registry registry;
+
+    const auto prototype = registry.create();
+    registry.assign<int>(prototype, 3);
+    registry.assign<char>(prototype, 'c');
+
+    const auto full = registry.create(prototype, registry);
+
+    ASSERT_TRUE((registry.has<int, char>(full)));
+    ASSERT_EQ(registry.get<int>(full), 3);
+    ASSERT_EQ(registry.get<char>(full), 'c');
+
+    const auto partial = registry.create<int>(prototype, registry);
+
+    ASSERT_TRUE(registry.has<int>(partial));
+    ASSERT_FALSE(registry.has<char>(partial));
+    ASSERT_EQ(registry.get<int>(partial), 3);
+
+    const auto exclude = registry.create(prototype, registry, entt::exclude<int>);
+
+    ASSERT_FALSE(registry.has<int>(exclude));
+    ASSERT_TRUE(registry.has<char>(exclude));
+    ASSERT_EQ(registry.get<char>(exclude), 'c');
+}
+
+TEST(Registry, CreateManyFromPrototype) {
+    entt::registry registry;
+    entt::entity entities[2];
+
+    const auto prototype = registry.create();
+    registry.assign<int>(prototype, 3);
+    registry.assign<char>(prototype, 'c');
+
+    registry.create(std::begin(entities), std::end(entities), prototype, registry);
+
+    ASSERT_TRUE((registry.has<int, char>(entities[0])));
+    ASSERT_TRUE((registry.has<int, char>(entities[1])));
+    ASSERT_EQ(registry.get<int>(entities[0]), 3);
+    ASSERT_EQ(registry.get<char>(entities[1]), 'c');
+
+    registry.create<int>(std::begin(entities), std::end(entities), prototype, registry);
+
+    ASSERT_TRUE(registry.has<int>(entities[0]));
+    ASSERT_FALSE(registry.has<char>(entities[1]));
+    ASSERT_EQ(registry.get<int>(entities[0]), 3);
+
+    registry.create(std::begin(entities), std::end(entities), prototype, registry, entt::exclude<int>);
+
+    ASSERT_FALSE(registry.has<int>(entities[0]));
+    ASSERT_TRUE(registry.has<char>(entities[1]));
+    ASSERT_EQ(registry.get<char>(entities[0]), 'c');
+}
+
+TEST(Registry, CreateFromPrototypeWithListener) {
+    entt::registry registry;
+    entt::entity entities[3];
+    listener listener;
+
+    const auto prototype = registry.create();
+    registry.assign<int>(prototype, 3);
+    registry.assign<char>(prototype, 'c');
+    registry.assign<empty_type>(prototype);
+
+    registry.on_construct<int>().connect<&listener::incr<int>>(listener);
+    registry.create<int, char>(std::begin(entities), std::end(entities), prototype, registry);
+
+    ASSERT_EQ(listener.counter, 3);
+
+    registry.on_construct<int>().disconnect<&listener::incr<int>>(listener);
+    registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener);
+    registry.create<char, empty_type>(std::begin(entities), std::end(entities), prototype, registry);
+
+    ASSERT_EQ(listener.counter, 6);
+}
+
 TEST(Registry, NonOwningGroupInterleaved) {
     entt::registry registry;
     typename entt::entity entity = entt::null;
@@ -1416,67 +1492,82 @@ TEST(Registry, CloneMoveOnlyComponent) {
 TEST(Registry, Stomp) {
     entt::registry registry;
 
-    const auto entity = registry.create();
-    registry.assign<int>(entity, 3);
-    registry.assign<char>(entity, 'c');
+    const auto prototype = registry.create();
+    registry.assign<int>(prototype, 3);
+    registry.assign<char>(prototype, 'c');
 
-    auto other = registry.create();
-    registry.stomp<int, char, double>(entity, registry, other);
+    auto entity = registry.create();
+    registry.stomp<int, char, double>(entity, prototype, registry);
 
-    ASSERT_TRUE(registry.has<int>(other));
-    ASSERT_TRUE(registry.has<char>(other));
-    ASSERT_EQ(registry.get<int>(other), 3);
-    ASSERT_EQ(registry.get<char>(other), 'c');
+    ASSERT_TRUE((registry.has<int, char>(entity)));
+    ASSERT_EQ(registry.get<int>(entity), 3);
+    ASSERT_EQ(registry.get<char>(entity), 'c');
 
-    registry.replace<int>(entity, 42);
-    registry.replace<char>(entity, 'a');
-    registry.stomp<int>(entity, registry, other);
+    registry.replace<int>(prototype, 42);
+    registry.replace<char>(prototype, 'a');
+    registry.stomp<int>(entity, prototype, registry);
 
-    ASSERT_EQ(registry.get<int>(other), 42);
-    ASSERT_EQ(registry.get<char>(other), 'c');
+    ASSERT_EQ(registry.get<int>(entity), 42);
+    ASSERT_EQ(registry.get<char>(entity), 'c');
 }
 
 TEST(Registry, StompExclude) {
     entt::registry registry;
 
+    const auto prototype = registry.create();
+    registry.assign<int>(prototype, 3);
+    registry.assign<char>(prototype, 'c');
+
     const auto entity = registry.create();
-    registry.assign<int>(entity, 3);
-    registry.assign<char>(entity, 'c');
+    registry.stomp(entity, prototype, registry, entt::exclude<char>);
 
-    const auto other = registry.create();
-    registry.stomp<int, char>(entity, registry, other, entt::exclude<char>);
+    ASSERT_TRUE(registry.has<int>(entity));
+    ASSERT_FALSE(registry.has<char>(entity));
+    ASSERT_EQ(registry.get<int>(entity), 3);
+
+    registry.replace<int>(prototype, 42);
+    registry.stomp(entity, prototype, registry, entt::exclude<int>);
+
+    ASSERT_TRUE((registry.has<int, char>(entity)));
+    ASSERT_EQ(registry.get<int>(entity), 3);
+    ASSERT_EQ(registry.get<char>(entity), 'c');
+
+    registry.remove<int>(entity);
+    registry.remove<char>(entity);
+    registry.stomp(entity, prototype, registry, entt::exclude<int, char>);
 
-    ASSERT_TRUE(registry.has<int>(other));
-    ASSERT_FALSE(registry.has<char>(other));
-    ASSERT_EQ(registry.get<int>(other), 3);
+    ASSERT_TRUE(registry.orphan(entity));
+}
 
-    registry.replace<int>(entity, 42);
-    registry.stomp(entity, registry, other, entt::exclude<int>);
+TEST(Registry, StompMulti) {
+    entt::registry registry;
 
-    ASSERT_TRUE(registry.has<int>(other));
-    ASSERT_TRUE(registry.has<char>(other));
-    ASSERT_EQ(registry.get<int>(other), 3);
-    ASSERT_EQ(registry.get<char>(other), 'c');
+    const auto prototype = registry.create();
+    registry.assign<int>(prototype, 3);
+    registry.assign<char>(prototype, 'c');
 
-    registry.remove<int>(other);
-    registry.remove<char>(other);
-    registry.stomp(entity, registry, other, entt::exclude<int, char>);
+    entt::entity entities[2];
+    registry.create(std::begin(entities), std::end(entities));
+    registry.stomp(std::begin(entities), std::end(entities), prototype, registry);
 
-    ASSERT_TRUE(registry.orphan(other));
+    ASSERT_TRUE((registry.has<int, char>(entities[0])));
+    ASSERT_TRUE((registry.has<int, char>(entities[1])));
+    ASSERT_EQ(registry.get<int>(entities[0]), 3);
+    ASSERT_EQ(registry.get<char>(entities[1]), 'c');
 }
 
 TEST(Registry, StompMoveOnlyComponent) {
     entt::registry registry;
-    const auto entity = registry.create();
 
-    registry.assign<std::unique_ptr<int>>(entity);
-    registry.assign<char>(entity);
+    const auto prototype = registry.create();
+    registry.assign<std::unique_ptr<int>>(prototype);
+    registry.assign<char>(prototype);
 
-    const auto other = registry.create();
-    registry.stomp(entity, registry, other);
+    const auto entity = registry.create();
+    registry.stomp(entity, prototype, registry);
 
-    ASSERT_TRUE(registry.has<char>(other));
-    ASSERT_FALSE(registry.has<std::unique_ptr<int>>(other));
+    ASSERT_TRUE(registry.has<char>(entity));
+    ASSERT_FALSE(registry.has<std::unique_ptr<int>>(entity));
 }
 
 TEST(Registry, GetOrAssign) {