Browse Source

storage: non-copyable allocator aware container

Michele Caini 4 years ago
parent
commit
0b79d99cac
3 changed files with 139 additions and 14 deletions
  1. 4 0
      src/entt/entity/sparse_set.hpp
  2. 37 14
      src/entt/entity/storage.hpp
  3. 98 0
      test/entt/entity/storage.cpp

+ 4 - 0
src/entt/entity/sparse_set.hpp

@@ -241,6 +241,9 @@ class basic_sparse_set {
     }
 
 protected:
+    /*! @brief Exchanges the contents with those of a given sparse set. */
+    virtual void swap_contents(basic_sparse_set &) {}
+
     /*! @brief Swaps two entities in the internal packed array. */
     virtual void swap_at(const std::size_t, const std::size_t) {}
 
@@ -385,6 +388,7 @@ public:
      */
     void swap(basic_sparse_set &other) {
         using std::swap;
+        swap_contents(other);
         propagate_on_container_swap(reserved.first(), other.reserved.first());
         swap(reserved.second(), other.reserved.second());
         swap(sparse_array, other.sparse_array);

+ 37 - 14
src/entt/entity/storage.hpp

@@ -263,18 +263,30 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
 
 protected:
     /**
-     * @brief Swaps two entities in the internal packed array.
-     * @param lhs A valid position of an entity within storage.
-     * @param rhs A valid position of an entity within storage.
+     * @brief Exchanges the contents with those of a given storage.
+     * @param base Reference to base storage to exchange the content with.
+     */
+    void swap_contents(basic_sparse_set &base) override {
+        using std::swap;
+        auto &other = static_cast<basic_storage &>(base);
+        propagate_on_container_swap(bucket.first(), other.bucket.first());
+        swap(bucket.second(), other.bucket.second());
+        swap(packed, other.packed);
+    }
+
+    /**
+     * @brief Swaps two elements in the internal packed array.
+     * @param lhs A valid position of an element within storage.
+     * @param rhs A valid position of an element within storage.
      */
     void swap_at(const std::size_t lhs, const std::size_t rhs) final {
         std::swap(packed[page(lhs)][offset(lhs)], packed[page(rhs)][offset(rhs)]);
     }
 
     /**
-     * @brief Moves an entity in the internal packed array.
-     * @param from A valid position of an entity within storage.
-     * @param to A valid position of an entity within storage.
+     * @brief Moves an element in the internal packed array.
+     * @param from A valid position of an element within storage.
+     * @param to A valid position of an element within storage.
      */
     void move_and_pop(const std::size_t from, const std::size_t to) final {
         auto &&elem = packed[page(from)][offset(from)];
@@ -283,7 +295,7 @@ protected:
     }
 
     /**
-     * @brief Attempts to erase an entity from the internal packed array.
+     * @brief Attempts to erase an element from the internal packed array.
      * @param entt A valid identifier.
      * @param ud Optional user data that are forwarded as-is to derived classes.
      */
@@ -303,8 +315,8 @@ protected:
     }
 
     /**
-     * @brief Attempts to erase an entity from the internal packed array.
-     * @param entt A valid identifier.
+     * @brief Erases an element from the internal packed array.
+     * @param pos A valid position of an element within the storage.
      * @param ud Optional user data that are forwarded as-is to derived classes.
      */
     void in_place_pop(const Entity entt, void *ud) override {
@@ -361,10 +373,23 @@ public:
      */
     basic_storage(basic_storage &&other) ENTT_NOEXCEPT
         : base_type{std::move(other)},
-          bucket{std::move(other.bucket)},
+          bucket{std::move(other.bucket.first()), std::exchange(other.bucket.second(), size_type{})},
           packed{std::exchange(other.packed, alloc_ptr_pointer{})}
     {}
 
+    /**
+     * @brief Allocator-extended move constructor.
+     * @param other The instance to move from.
+     * @param allocator The allocator to use.
+     */
+    basic_storage(basic_storage &&other, const allocator_type &allocator) ENTT_NOEXCEPT
+        : base_type{std::move(other), allocator},
+          bucket{allocator, std::exchange(other.bucket.second(), size_type{})},
+          packed{std::exchange(other.packed, alloc_ptr_pointer{})}
+    {
+        ENTT_ASSERT(alloc_traits::is_always_equal::value || bucket.first() == other.bucket.first(), "Copying a storage is not allowed");
+    }
+
     /*! @brief Default destructor. */
     ~basic_storage() override {
         release_memory();
@@ -377,12 +402,10 @@ public:
      */
     basic_storage & operator=(basic_storage &&other) ENTT_NOEXCEPT {
         release_memory();
-
         base_type::operator=(std::move(other));
-
-        bucket = std::move(other.bucket);
+        propagate_on_container_move_assignment(bucket.first(), other.bucket.first());
+        bucket.second() = std::exchange(other.bucket.second(), size_type{});
         packed = std::exchange(other.packed, alloc_ptr_pointer{});
-
         return *this;
     }
 

+ 98 - 0
test/entt/entity/storage.cpp

@@ -152,6 +152,56 @@ TEST(Storage, Move) {
     ASSERT_EQ(other.get(entt::entity{3}), 3);
 }
 
+TEST(Storage, Swap) {
+    entt::storage<int> pool;
+    entt::storage<int> other;
+
+    pool.emplace(entt::entity{42}, 41);
+
+    other.emplace(entt::entity{9}, 8);
+    other.emplace(entt::entity{3}, 2);
+    other.erase(entt::entity{9});
+
+    ASSERT_EQ(pool.size(), 1u);
+    ASSERT_EQ(other.size(), 1u);
+
+    pool.swap(other);
+
+    ASSERT_EQ(pool.size(), 1u);
+    ASSERT_EQ(other.size(), 1u);
+
+    ASSERT_EQ(pool.at(0u), entt::entity{3});
+    ASSERT_EQ(pool.get(entt::entity{3}), 2);
+
+    ASSERT_EQ(other.at(0u), entt::entity{42});
+    ASSERT_EQ(other.get(entt::entity{42}), 41);
+}
+
+TEST(Storage, StableSwap) {
+    entt::storage<stable_type> pool;
+    entt::storage<stable_type> other;
+
+    pool.emplace(entt::entity{42}, 41);
+
+    other.emplace(entt::entity{9}, 8);
+    other.emplace(entt::entity{3}, 2);
+    other.erase(entt::entity{9});
+
+    ASSERT_EQ(pool.size(), 1u);
+    ASSERT_EQ(other.size(), 2u);
+
+    pool.swap(other);
+
+    ASSERT_EQ(pool.size(), 2u);
+    ASSERT_EQ(other.size(), 1u);
+
+    ASSERT_EQ(pool.at(1u), entt::entity{3});
+    ASSERT_EQ(pool.get(entt::entity{3}).value, 2);
+
+    ASSERT_EQ(other.at(0u), entt::entity{42});
+    ASSERT_EQ(other.get(entt::entity{42}).value, 41);
+}
+
 TEST(Storage, EmptyType) {
     entt::storage<empty_type> pool;
     pool.emplace(entt::entity{99});
@@ -1128,6 +1178,54 @@ TEST(Storage, ThrowingComponent) {
     ASSERT_EQ(pool.get(entt::entity{42}), 42);
 }
 
+TEST(Storage, CustomAllocator) {
+    test::throwing_allocator<entt::entity> allocator{};
+    entt::basic_storage<entt::entity, int, test::throwing_allocator<entt::entity>> pool{allocator};
+
+    ASSERT_EQ(pool.get_allocator(), allocator);
+
+    pool.reserve(1u);
+
+    ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
+
+    pool.emplace(entt::entity{0}, 3);
+    pool.emplace(entt::entity{1}, 42);
+
+    entt::basic_storage<entt::entity, int, test::throwing_allocator<entt::entity>> other{std::move(pool), allocator};
+
+    ASSERT_TRUE(pool.empty());
+    ASSERT_FALSE(other.empty());
+    ASSERT_EQ(pool.capacity(), 0u);
+    ASSERT_EQ(other.capacity(), ENTT_PACKED_PAGE);
+    ASSERT_EQ(other.size(), 2u);
+
+    pool = std::move(other);
+
+    ASSERT_FALSE(pool.empty());
+    ASSERT_TRUE(other.empty());
+    ASSERT_EQ(other.capacity(), 0u);
+    ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
+    ASSERT_EQ(pool.size(), 2u);
+
+    pool.swap(other);
+    pool = std::move(other);
+
+    ASSERT_FALSE(pool.empty());
+    ASSERT_TRUE(other.empty());
+    ASSERT_EQ(other.capacity(), 0u);
+    ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
+    ASSERT_EQ(pool.size(), 2u);
+
+    pool.clear();
+
+    ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
+    ASSERT_EQ(pool.size(), 0u);
+
+    pool.shrink_to_fit();
+
+    ASSERT_EQ(pool.capacity(), 0u);
+}
+
 TEST(Storage, ThrowingAllocator) {
     entt::basic_storage<entt::entity, int, test::throwing_allocator<int>> pool;