Michele Caini 7 лет назад
Родитель
Сommit
960bbbde29

+ 52 - 21
src/entt/entity/registry.hpp

@@ -53,6 +53,15 @@ class registry {
             : reg{reg}
         {}
 
+        component_pool(const component_pool &other)
+            : sparse_set<Entity, Component>{other}, ctor{}, dtor{}, reg{other.reg}
+        {}
+
+        component_pool & operator=(const component_pool &other) {
+            sparse_set<Entity, Component>::operator=(other);
+            reg = other.reg;
+        }
+
         template<typename... Args>
         Component & construct(const Entity entity, Args &&... args) {
             auto &component = sparse_set<Entity, Component>::construct(entity, std::forward<Args>(args)...);
@@ -65,6 +74,14 @@ class registry {
             sparse_set<Entity, Component>::destroy(entity);
         }
 
+        std::unique_ptr<sparse_set<Entity>> clone() const override {
+            if constexpr(std::is_copy_constructible_v<Component>) {
+                return std::make_unique<component_pool>(*this);
+            } else {
+                return nullptr;
+            }
+        }
+
         typename component_signal_type::sink_type construction() ENTT_NOEXCEPT {
             return ctor.sink();
         }
@@ -1279,37 +1296,51 @@ public:
      * @brief Clones the given components and all the entity identifiers.
      *
      * The components must be copiable for obvious reasons. The entities
-     * maintain their versions once copied.
+     * maintain their versions once copied.<br/>
+     * If no components are provided, the registry will try to clone all the
+     * existing pools.
+     *
+     * @warning
+     * Attempting to clone components that aren't copyable can result in
+     * unexpected behaviors.<br/>
+     * A static assertion will abort the compilation when one or more components
+     * are provided at the call site. Otherwise, an assertion will abort the
+     * execution at runtime in debug mode in case one or more pools cannot be
+     * cloned.
      *
      * @note
      * There isn't an efficient way to know if all the entities are assigned at
      * least one component once copied. Therefore, there may be orphans. It is
      * up to the caller to clean up the registry if necessary.
      *
-     * @warning
-     * This function requires that the registry be empty. In case it isn't, all
-     * the data will be automatically deleted beforehand.
-     *
      * @tparam Component Types of components to clone.
-     * @param reg A valid reference to a source registry.
+     * @return A fresh copy of the registry.
      */
     template<typename... Component>
-    void clone(const registry &reg, type_list<Component...> = {}) {
-        *this = {};
+    registry clone() const {
+        registry other;
+        other.pools.resize(pools.size());
 
-        (assure<Component>(), ...);
-        (reserve<Component>(reg.size<Component>()), ...);
-
-        (std::copy(reg.raw<Component>(), reg.raw<Component>() + reg.size<Component>(), pool<Component>().raw()), ...);
-        // double lambda function used to work around a bug of gcc7
-        (std::for_each(reg.data<Component>(), reg.data<Component>() + reg.size<Component>(), ([](auto *cpool) {
-            return [cpool](const auto entity) { cpool->construct(entity); };
-        })(pools[component_family::type<Component>].get())), ...);
-
-        next = reg.next;
-        available = reg.available;
-        entities.resize(reg.entities.size());
-        std::copy(reg.entities.cbegin(), reg.entities.cend(), entities.begin());
+        if(sizeof...(Component)) {
+            static_assert(std::conjunction_v<std::is_copy_constructible<Component>...>);
+            ((other.pools[component_family::type<Component>] = managed<Component>() ? pool<Component>().clone() : nullptr), ...);
+        } else {
+            for(auto pos = pools.size(); pos; --pos) {
+                auto &cpool = pools[pos-1];
+
+                if(cpool) {
+                    other.pools[pos-1] = cpool->clone();
+                    assert(other.pools[pos-1]);
+                }
+            };
+        }
+
+        other.next = next;
+        other.available = available;
+        other.entities.resize(entities.size());
+        std::copy(entities.cbegin(), entities.cend(), other.entities.begin());
+
+        return other;
     }
 
     /**

+ 42 - 26
src/entt/entity/sparse_set.hpp

@@ -7,6 +7,7 @@
 #include <numeric>
 #include <utility>
 #include <vector>
+#include <memory>
 #include <cstddef>
 #include <cassert>
 #include <type_traits>
@@ -80,7 +81,10 @@ class sparse_set<Entity> {
         iterator() ENTT_NOEXCEPT = default;
 
         iterator(const iterator &) ENTT_NOEXCEPT = default;
+        iterator(iterator &&) ENTT_NOEXCEPT = default;
+
         iterator & operator=(const iterator &) ENTT_NOEXCEPT = default;
+        iterator & operator=(iterator &&) ENTT_NOEXCEPT = default;
 
         iterator & operator++() ENTT_NOEXCEPT {
             return --index, *this;
@@ -172,22 +176,9 @@ public:
     /*! @brief Input iterator type. */
     using iterator_type = iterator;
 
-    /*! @brief Default constructor. */
-    sparse_set() ENTT_NOEXCEPT = default;
-
     /*! @brief Default destructor. */
     virtual ~sparse_set() ENTT_NOEXCEPT = default;
 
-    /*! @brief Copying a sparse set isn't allowed. */
-    sparse_set(const sparse_set &) = delete;
-    /*! @brief Default move constructor. */
-    sparse_set(sparse_set &&) = default;
-
-    /*! @brief Copying a sparse set isn't allowed. @return This sparse set. */
-    sparse_set & operator=(const sparse_set &) = delete;
-    /*! @brief Default move assignment operator. @return This sparse set. */
-    sparse_set & operator=(sparse_set &&) = default;
-
     /**
      * @brief Increases the capacity of a sparse set.
      *
@@ -477,6 +468,23 @@ public:
         direct.clear();
     }
 
+    /**
+     * @brief Clones and returns a sparse set.
+     *
+     * The basic implementation of a sparse set is always copyable. Therefore,
+     * the returned instance is always valid.
+     *
+     * @return A fresh copy of the given sparse set.
+     */
+    virtual std::unique_ptr<sparse_set> clone() const {
+        auto other = std::make_unique<sparse_set>();
+        other->reverse.resize(reverse.size());
+        other->direct.resize(direct.size());
+        std::copy(reverse.cbegin(), reverse.cend(), other->reverse.begin());
+        std::copy(direct.cbegin(), direct.cend(), other->direct.begin());
+        return other;
+    }
+
 private:
     std::vector<entity_type> reverse;
     std::vector<entity_type> direct;
@@ -531,7 +539,10 @@ class sparse_set<Entity, Type>: public sparse_set<Entity> {
         iterator() ENTT_NOEXCEPT = default;
 
         iterator(const iterator &) ENTT_NOEXCEPT = default;
+        iterator(iterator &&) ENTT_NOEXCEPT = default;
+
         iterator & operator=(const iterator &) ENTT_NOEXCEPT = default;
+        iterator & operator=(iterator &&) ENTT_NOEXCEPT = default;
 
         iterator & operator++() ENTT_NOEXCEPT {
             return --index, *this;
@@ -627,19 +638,6 @@ public:
     /*! @brief Constant input iterator type. */
     using const_iterator_type = iterator<true>;
 
-    /*! @brief Default constructor. */
-    sparse_set() ENTT_NOEXCEPT = default;
-
-    /*! @brief Copying a sparse set isn't allowed. */
-    sparse_set(const sparse_set &) = delete;
-    /*! @brief Default move constructor. */
-    sparse_set(sparse_set &&) = default;
-
-    /*! @brief Copying a sparse set isn't allowed. @return This sparse set. */
-    sparse_set & operator=(const sparse_set &) = delete;
-    /*! @brief Default move assignment operator. @return This sparse set. */
-    sparse_set & operator=(sparse_set &&) = default;
-
     /**
      * @brief Increases the capacity of a sparse set.
      *
@@ -939,6 +937,24 @@ public:
         instances.clear();
     }
 
+    /**
+     * @brief Clones and returns a sparse set if possible.
+     *
+     * The extended implementation of a sparse set is copyable only if its
+     * object type is copyable. Because of that, this member functions isn't
+     * guaranteed to return always a valid pointer.
+     *
+     * @return A fresh copy of the given sparse set if its object type is
+     * copyable, an empty unique pointer otherwise.
+     */
+    std::unique_ptr<sparse_set<Entity>> clone() const override {
+        if constexpr(std::is_copy_constructible_v<Type>) {
+            return std::make_unique<sparse_set>(*this);
+        } else {
+            return nullptr;
+        }
+    }
+
 private:
     std::vector<object_type> instances;
 };

+ 55 - 6
test/entt/entity/registry.cpp

@@ -2,6 +2,7 @@
 #include <unordered_set>
 #include <functional>
 #include <iterator>
+#include <memory>
 #include <type_traits>
 #include <gtest/gtest.h>
 #include <entt/entity/entt_traits.hpp>
@@ -781,10 +782,6 @@ TEST(Registry, Clone) {
     entt::registry<> registry;
     entt::registry<> other;
 
-    const auto entity = other.create();
-    other.assign<int>(entity, 42);
-    other.assign<char>(entity, 'c');
-
     registry.destroy(registry.create());
 
     const auto e0 = registry.create();
@@ -801,9 +798,12 @@ TEST(Registry, Clone) {
     registry.assign<char>(e2, '2');
 
     registry.destroy(e1);
-    other.clone<int, char>(registry);
 
-    ASSERT_FALSE(other.valid(entity));
+    other = registry.clone<int, char>();
+
+    ASSERT_EQ(other.size(), registry.size());
+    ASSERT_EQ(other.alive(), registry.alive());
+
     ASSERT_TRUE(other.valid(e0));
     ASSERT_FALSE(other.valid(e1));
     ASSERT_TRUE(other.valid(e2));
@@ -815,4 +815,53 @@ TEST(Registry, Clone) {
     ASSERT_EQ(other.get<int>(e0), 0);
     ASSERT_EQ(other.get<int>(e2), 2);
     ASSERT_EQ(other.get<char>(e2), '2');
+
+    other = registry.clone();
+
+    ASSERT_EQ(other.size(), registry.size());
+    ASSERT_EQ(other.alive(), registry.alive());
+
+    ASSERT_TRUE(other.valid(e0));
+    ASSERT_FALSE(other.valid(e1));
+    ASSERT_TRUE(other.valid(e2));
+
+    ASSERT_TRUE((other.has<int, double>(e0)));
+    ASSERT_TRUE((other.has<int, char>(e2)));
+
+    ASSERT_EQ(other.get<int>(e0), 0);
+    ASSERT_EQ(other.get<double>(e0), 0.);
+    ASSERT_EQ(other.get<int>(e2), 2);
+    ASSERT_EQ(other.get<char>(e2), '2');
+
+    other = registry.clone<char>();
+
+    ASSERT_EQ(other.size(), registry.size());
+    ASSERT_EQ(other.alive(), registry.alive());
+
+    ASSERT_TRUE(other.valid(e0));
+    ASSERT_FALSE(other.valid(e1));
+    ASSERT_TRUE(other.valid(e2));
+
+    ASSERT_FALSE((other.has<int>(e0)));
+    ASSERT_FALSE((other.has<double>(e0)));
+    ASSERT_FALSE((other.has<int>(e2)));
+    ASSERT_TRUE((other.has<char>(e2)));
+
+    ASSERT_TRUE(other.orphan(e0));
+    ASSERT_EQ(other.get<char>(e2), '2');
+
+    const auto entity = registry.create();
+    listener listener;
+
+    ASSERT_NE(e1, entity);
+    ASSERT_EQ(registry.entity(e1), registry.entity(entity));
+
+    registry.construction<char>().connect<&listener::incr<char>>(&listener);
+    registry.destruction<char>().connect<&listener::decr<char>>(&listener);
+    registry.assign<char>(entity, 'e');
+    registry.assign<char>(e0, '0');
+    registry.remove<char>(e0);
+
+    ASSERT_EQ(listener.counter, 1);
+    ASSERT_EQ(listener.last, e0);
 }

+ 2 - 10
test/entt/entity/sparse_set.cpp

@@ -1,3 +1,4 @@
+#include <memory>
 #include <unordered_set>
 #include <gtest/gtest.h>
 #include <entt/entity/sparse_set.hpp>
@@ -839,16 +840,7 @@ TEST(SparseSetWithType, ReferencesGuaranteed) {
 }
 
 TEST(SparseSetWithType, MoveOnlyComponent) {
-    struct move_only_component {
-        move_only_component() = default;
-        ~move_only_component() = default;
-        move_only_component(const move_only_component &) = delete;
-        move_only_component(move_only_component &&) = default;
-        move_only_component & operator=(const move_only_component &) = delete;
-        move_only_component & operator=(move_only_component &&) = default;
-    };
-
     // the purpose is to ensure that move only components are always accepted
-    entt::sparse_set<std::uint64_t, move_only_component> set;
+    entt::sparse_set<std::uint64_t, std::unique_ptr<int>> set;
     (void)set;
 }