diff --git a/TODO b/TODO index 91b92455b..8485ab573 100644 --- a/TODO +++ b/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 diff --git a/src/entt/entity/registry.hpp b/src/entt/entity/registry.hpp index 34a7cb3be..e2fcb27bd 100644 --- a/src/entt/entity/registry.hpp +++ b/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 + void shrink_to_fit() { + assure()->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. diff --git a/src/entt/entity/sparse_set.hpp b/src/entt/entity/sparse_set.hpp index 00f54b93e..fd55a3008 100644 --- a/src/entt/entity/sparse_set.hpp +++ b/src/entt/entity/sparse_set.hpp @@ -169,10 +169,10 @@ class sparse_set { reverse.resize(page+1); } - if(!reverse[page]) { - reverse[page] = std::make_unique(entt_per_page); + if(!reverse[page].first) { + reverse[page].first = std::make_unique(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> reverse; + std::vector, size_type>> reverse; std::vector 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) { + instances.shrink_to_fit(); + } + } + /** * @brief Direct access to the array of objects. * diff --git a/test/entt/entity/registry.cpp b/test/entt/entity/registry.cpp index 94baca695..a080b42c5 100644 --- a/test/entt/entity/registry.cpp +++ b/test/entt/entity/registry.cpp @@ -240,6 +240,15 @@ TEST(Registry, Functionalities) { ASSERT_EQ(registry.size(), entt::registry::size_type{0}); ASSERT_EQ(registry.size(), entt::registry::size_type{0}); ASSERT_TRUE(registry.empty()); + + ASSERT_EQ(registry.capacity(), entt::registry::size_type{8}); + ASSERT_EQ(registry.capacity(), entt::registry::size_type{8}); + + registry.shrink_to_fit(); + registry.shrink_to_fit(); + + ASSERT_EQ(registry.capacity(), entt::registry::size_type{}); + ASSERT_EQ(registry.capacity(), entt::registry::size_type{}); } TEST(Registry, Identifiers) { diff --git a/test/entt/entity/sparse_set.cpp b/test/entt/entity/sparse_set.cpp index c75aff3d0..f8196edba 100644 --- a/test/entt/entity/sparse_set.cpp +++ b/test/entt/entity/sparse_set.cpp @@ -83,7 +83,7 @@ TEST(SparseSetNoType, Pagination) { entt::sparse_set 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::move(set)}; entt::sparse_set other;