shrink_to_fit to release pages

This commit is contained in:
Michele Caini
2019-03-30 00:09:49 +01:00
parent 2f2edfbde8
commit dc28066017
5 changed files with 103 additions and 28 deletions

1
TODO
View File

@@ -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

View File

@@ -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.

View File

@@ -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.
*

View File

@@ -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) {

View File

@@ -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;