Browse Source

shrink_to_fit to release pages

Michele Caini 7 years ago
parent
commit
dc28066017
5 changed files with 103 additions and 28 deletions
  1. 0 1
      TODO
  2. 9 0
      src/entt/entity/registry.hpp
  3. 50 15
      src/entt/entity/sparse_set.hpp
  4. 9 0
      test/entt/entity/registry.cpp
  5. 35 12
      test/entt/entity/sparse_set.cpp

+ 0 - 1
TODO

@@ -27,4 +27,3 @@
 * review sparse set to allow customization (mix pack in the spec, base is position only)
   - non-owning groups can iterate pages and skip empty ones, this should mitigate the lack of the packed array
 * review 64 bit id: user defined area + dedicated member on the registry to set it
-* improve pagination: use a support integer to count elements and remove pages when empty

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

@@ -378,6 +378,15 @@ public:
         return cpool ? cpool->empty() : true;
     }
 
+    /**
+     * @brief Requests the removal of unused capacity for a given component.
+     * @tparam Component Type of component for which to reclaim unused capacity.
+     */
+    template<typename Component>
+    void shrink_to_fit() {
+        assure<Component>()->shrink_to_fit();
+    }
+
     /**
      * @brief Checks if there exists at least an entity still in use.
      * @return True if at least an entity is still in use, false otherwise.

+ 50 - 15
src/entt/entity/sparse_set.hpp

@@ -169,10 +169,10 @@ class sparse_set<Entity> {
             reverse.resize(page+1);
         }
 
-        if(!reverse[page]) {
-            reverse[page] = std::make_unique<entity_type[]>(entt_per_page);
+        if(!reverse[page].first) {
+            reverse[page].first = std::make_unique<entity_type[]>(entt_per_page);
             // null is safe in all cases for our purposes
-            std::fill_n(reverse[page].get(), entt_per_page, null);
+            std::fill_n(reverse[page].first.get(), entt_per_page, null);
         }
     }
 
@@ -199,13 +199,14 @@ public:
      * @param other The instance to copy from.
      */
     sparse_set(const sparse_set &other)
-        : reverse(),
+        : reverse{},
           direct{other.direct}
     {
         for(size_type i = {}, last = other.reverse.size(); i < last; ++i) {
-            if(other.reverse[i]) {
+            if(other.reverse[i].first) {
                 assure(i);
-                std::copy_n(other.reverse[i].get(), entt_per_page, reverse[i].get());
+                std::copy_n(other.reverse[i].first.get(), entt_per_page, reverse[i].first.get());
+                reverse[i].second = other.reverse[i].second;
             }
         }
     }
@@ -254,6 +255,22 @@ public:
         return direct.capacity();
     }
 
+    /*! @brief Requests the removal of unused capacity. */
+    virtual void shrink_to_fit() {
+        while(!reverse.empty() && !reverse.back().second) {
+            reverse.pop_back();
+        }
+
+        for(auto &&data: reverse) {
+            if(!data.second) {
+                data.first.reset();
+            }
+        }
+
+        reverse.shrink_to_fit();
+        direct.shrink_to_fit();
+    }
+
     /**
      * @brief Returns the extent of a sparse set.
      *
@@ -361,7 +378,7 @@ public:
     bool has(const entity_type entt) const ENTT_NOEXCEPT {
         auto [page, offset] = index(entt);
         // testing against null permits to avoid accessing the direct vector
-        return (page < reverse.size() && reverse[page] && reverse[page][offset] != null);
+        return (page < reverse.size() && reverse[page].second && reverse[page].first[offset] != null);
     }
 
     /**
@@ -379,7 +396,7 @@ public:
     size_type get(const entity_type entt) const ENTT_NOEXCEPT {
         ENTT_ASSERT(has(entt));
         auto [page, offset] = index(entt);
-        return size_type(reverse[page][offset]);
+        return size_type(reverse[page].first[offset]);
     }
 
     /**
@@ -397,7 +414,8 @@ public:
         ENTT_ASSERT(!has(entt));
         auto [page, offset] = index(entt);
         assure(page);
-        reverse[page][offset] = entity_type(direct.size());
+        reverse[page].first[offset] = entity_type(direct.size());
+        reverse[page].second++;
         direct.push_back(entt);
     }
 
@@ -420,7 +438,8 @@ public:
             ENTT_ASSERT(!has(entt));
             auto [page, offset] = index(entt);
             assure(page);
-            reverse[page][offset] = next++;
+            reverse[page].first[offset] = next++;
+            reverse[page].second++;
         });
 
         direct.insert(direct.end(), first, last);
@@ -441,9 +460,10 @@ public:
         ENTT_ASSERT(has(entt));
         auto [from_page, from_offset] = index(entt);
         auto [to_page, to_offset] = index(direct.back());
-        std::swap(direct[size_type(reverse[from_page][from_offset])], direct.back());
-        std::swap(reverse[from_page][from_offset], reverse[to_page][to_offset]);
-        reverse[from_page][from_offset] = null;
+        std::swap(direct[size_type(reverse[from_page].first[from_offset])], direct.back());
+        std::swap(reverse[from_page].first[from_offset], reverse[to_page].first[to_offset]);
+        reverse[from_page].first[from_offset] = null;
+        reverse[from_page].second--;
         direct.pop_back();
     }
 
@@ -467,7 +487,7 @@ public:
         ENTT_ASSERT(rhs < direct.size());
         auto [src_page, src_offset] = index(direct[lhs]);
         auto [dst_page, dst_offset] = index(direct[rhs]);
-        std::swap(reverse[src_page][src_offset], reverse[dst_page][dst_offset]);
+        std::swap(reverse[src_page].first[src_offset], reverse[dst_page].first[dst_offset]);
         std::swap(direct[lhs], direct[rhs]);
     }
 
@@ -530,7 +550,7 @@ public:
     }
 
 private:
-    std::vector<std::unique_ptr<entity_type[]>> reverse;
+    std::vector<std::pair<std::unique_ptr<entity_type[]>, size_type>> reverse;
     std::vector<entity_type> direct;
 };
 
@@ -792,6 +812,21 @@ public:
         }
     }
 
+    /**
+     * @brief Requests the removal of unused capacity.
+     *
+     * @note
+     * Empty components aren't explicitly instantiated. Only one instance of the
+     * given type is created. Therefore, this function does nothing.
+     */
+    void shrink_to_fit() override {
+        underlying_type::shrink_to_fit();
+
+        if constexpr(!std::is_empty_v<object_type>) {
+            instances.shrink_to_fit();
+        }
+    }
+
     /**
      * @brief Direct access to the array of objects.
      *

+ 9 - 0
test/entt/entity/registry.cpp

@@ -240,6 +240,15 @@ TEST(Registry, Functionalities) {
     ASSERT_EQ(registry.size<int>(), entt::registry::size_type{0});
     ASSERT_EQ(registry.size<char>(), entt::registry::size_type{0});
     ASSERT_TRUE(registry.empty<int>());
+
+    ASSERT_EQ(registry.capacity<int>(), entt::registry::size_type{8});
+    ASSERT_EQ(registry.capacity<char>(), entt::registry::size_type{8});
+
+    registry.shrink_to_fit<int>();
+    registry.shrink_to_fit<char>();
+
+    ASSERT_EQ(registry.capacity<int>(), entt::registry::size_type{});
+    ASSERT_EQ(registry.capacity<char>(), entt::registry::size_type{});
 }
 
 TEST(Registry, Identifiers) {

+ 35 - 12
test/entt/entity/sparse_set.cpp

@@ -83,7 +83,7 @@ TEST(SparseSetNoType, Pagination) {
     entt::sparse_set<std::uint64_t> set;
     constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(std::uint64_t);
 
-    ASSERT_EQ(set.extent(), 0u);
+    ASSERT_EQ(set.extent(), 0);
 
     set.construct(entt_per_page-1);
 
@@ -96,6 +96,23 @@ TEST(SparseSetNoType, Pagination) {
     ASSERT_TRUE(set.has(entt_per_page-1));
     ASSERT_TRUE(set.has(entt_per_page));
     ASSERT_FALSE(set.has(entt_per_page+1));
+
+    set.destroy(entt_per_page-1);
+
+    ASSERT_EQ(set.extent(), 2 * entt_per_page);
+    ASSERT_FALSE(set.has(entt_per_page-1));
+    ASSERT_TRUE(set.has(entt_per_page));
+
+    set.shrink_to_fit();
+    set.destroy(entt_per_page);
+
+    ASSERT_EQ(set.extent(), 2 * entt_per_page);
+    ASSERT_FALSE(set.has(entt_per_page-1));
+    ASSERT_FALSE(set.has(entt_per_page));
+
+    set.shrink_to_fit();
+
+    ASSERT_EQ(set.extent(), 0);
 }
 
 TEST(SparseSetNoType, BatchAdd) {
@@ -419,33 +436,33 @@ TEST(SparseSetWithType, Functionalities) {
     ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
     ASSERT_EQ(set.begin(), set.end());
     ASSERT_FALSE(set.has(0));
-    ASSERT_FALSE(set.has(42));
+    ASSERT_FALSE(set.has(41));
 
-    set.construct(42, 3);
+    set.construct(41, 3);
 
     ASSERT_FALSE(set.empty());
     ASSERT_EQ(set.size(), 1u);
     ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end());
     ASSERT_NE(set.begin(), set.end());
     ASSERT_FALSE(set.has(0));
-    ASSERT_TRUE(set.has(42));
-    ASSERT_EQ(set.get(42), 3);
-    ASSERT_EQ(*set.try_get(42), 3);
+    ASSERT_TRUE(set.has(41));
+    ASSERT_EQ(set.get(41), 3);
+    ASSERT_EQ(*set.try_get(41), 3);
     ASSERT_EQ(set.try_get(99), nullptr);
 
-    set.destroy(42);
+    set.destroy(41);
 
     ASSERT_TRUE(set.empty());
     ASSERT_EQ(set.size(), 0u);
     ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
     ASSERT_EQ(set.begin(), set.end());
     ASSERT_FALSE(set.has(0));
-    ASSERT_FALSE(set.has(42));
+    ASSERT_FALSE(set.has(41));
 
-    set.construct(42, 12);
+    set.construct(41, 12);
 
-    ASSERT_EQ(set.get(42), 12);
-    ASSERT_EQ(*set.try_get(42), 12);
+    ASSERT_EQ(set.get(41), 12);
+    ASSERT_EQ(*set.try_get(41), 12);
     ASSERT_EQ(set.try_get(99), nullptr);
 
     set.reset();
@@ -455,7 +472,13 @@ TEST(SparseSetWithType, Functionalities) {
     ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
     ASSERT_EQ(set.begin(), set.end());
     ASSERT_FALSE(set.has(0));
-    ASSERT_FALSE(set.has(42));
+    ASSERT_FALSE(set.has(41));
+
+    ASSERT_EQ(set.capacity(), 42);
+
+    set.shrink_to_fit();
+
+    ASSERT_EQ(set.capacity(), 0);
 
     (void)entt::sparse_set<std::uint64_t, int>{std::move(set)};
     entt::sparse_set<std::uint64_t, int> other;