* removed entt::page_size
* renamed ENTT_PAGE_SIZE to ENTT_SPARSE_PAGE
* added ENTT_PACKED_PAGE (page size for component arrays)
This commit is contained in:
Michele Caini
2021-05-24 23:29:55 +02:00
parent acf3d4cd74
commit 755699c31b
10 changed files with 85 additions and 68 deletions

View File

@@ -10,7 +10,8 @@
* [ENTT_NOEXCEPT](#entt_noexcept)
* [ENTT_USE_ATOMIC](#entt_use_atomic)
* [ENTT_ID_TYPE](#entt_id_type)
* [ENTT_PAGE_SIZE](#entt_page_size)
* [ENTT_SPARSE_PAGE](#entt_sparse_page)
* [ENTT_PACKED_PAGE](#entt_packed_page)
* [ENTT_ASSERT](#entt_assert)
* [ENTT_DISABLE_ASSERT](#entt_disable_assert)
* [ENTT_NO_ETO](#entt_no_eto)
@@ -59,14 +60,23 @@ the library.<br/>
By default, its type is `std::uint32_t`. However, users can define a different
default type if necessary.
## ENTT_PAGE_SIZE
## ENTT_SPARSE_PAGE
As is known, the ECS module of `EnTT` is based on _sparse sets_. What is less
known perhaps is that these are paged to reduce memory consumption.<br/>
It's known that the ECS module of `EnTT` is based on _sparse sets_. What is less
known perhaps is that the sparse arrays are paged to reduce memory usage.<br/>
Default size of pages (that is, the number of elements they contain) is 4096 but
users can adjust it if appropriate. In all case, the chosen value **must** be a
power of 2.
## ENTT_PACKED_PAGE
Similar to sparse arrays, packed arrays of components are paginated as well. In
However, int this case the aim isn't to reduce memory usage but to have pointer
stability upon component creation.<br/>
Default size of pages (that is, the number of elements they contain) is 1024 but
users can adjust it if appropriate. In all case, the chosen value **must** be a
power of 2.
## ENTT_ASSERT
For performance reasons, `EnTT` doesn't use exceptions or any other control

View File

@@ -29,10 +29,17 @@
#endif
#ifdef ENTT_PAGE_SIZE
static_assert(ENTT_PAGE_SIZE && ((ENTT_PAGE_SIZE & (ENTT_PAGE_SIZE - 1)) == 0), "ENTT_PAGE_SIZE must be a power of two");
#ifdef ENTT_SPARSE_PAGE
static_assert(ENTT_SPARSE_PAGE && ((ENTT_SPARSE_PAGE & (ENTT_SPARSE_PAGE - 1)) == 0), "ENTT_SPARSE_PAGE must be a power of two");
#else
# define ENTT_PAGE_SIZE 4096
# define ENTT_SPARSE_PAGE 4096
#endif
#ifdef ENTT_PACKED_PAGE
static_assert(ENTT_PACKED_PAGE && ((ENTT_PACKED_PAGE & (ENTT_PACKED_PAGE - 1)) == 0), "ENTT_PACKED_PAGE must be a power of two");
#else
# define ENTT_PACKED_PAGE 1024
#endif

View File

@@ -17,9 +17,6 @@ class basic_any;
using id_type = ENTT_ID_TYPE;
/*! @brief Default page size. */
inline static constexpr auto page_size = ENTT_PAGE_SIZE;
/*! @brief Alias declaration for the most common use case. */
using any = basic_any<>;

View File

@@ -152,8 +152,8 @@ Entity to_entity(const basic_registry<Entity> &reg, const Component &instance) {
const auto view = reg.template view<const Component>();
const auto *addr = std::addressof(instance);
for(auto it = view.rbegin(), last = view.rend(); it < last; it += page_size) {
if(const auto dist = (addr - std::addressof(view.template get<const Component>(*it))); dist >= 0 && dist < page_size) {
for(auto it = view.rbegin(), last = view.rend(); it < last; it += ENTT_PACKED_PAGE) {
if(const auto dist = (addr - std::addressof(view.template get<const Component>(*it))); dist >= 0 && dist < ENTT_PACKED_PAGE) {
return *(it + dist);
}
}

View File

@@ -43,6 +43,7 @@ namespace entt {
template<typename Entity, typename Allocator>
class basic_sparse_set {
static constexpr auto growth_factor = 1.5;
static constexpr auto sparse_page = ENTT_SPARSE_PAGE;
using traits_type = entt_traits<Entity>;
@@ -160,11 +161,11 @@ class basic_sparse_set {
};
[[nodiscard]] static auto page(const Entity entt) ENTT_NOEXCEPT {
return size_type{(to_integral(entt) & traits_type::entity_mask) / page_size};
return size_type{(to_integral(entt) & traits_type::entity_mask) / sparse_page};
}
[[nodiscard]] static auto offset(const Entity entt) ENTT_NOEXCEPT {
return size_type{to_integral(entt) & (page_size - 1)};
return size_type{to_integral(entt) & (sparse_page - 1)};
}
[[nodiscard]] auto assure_page(const std::size_t idx) {
@@ -182,8 +183,8 @@ class basic_sparse_set {
}
if(!sparse[idx]) {
sparse[idx] = alloc_traits::allocate(allocator, page_size);
std::uninitialized_fill(sparse[idx], sparse[idx] + page_size, null);
sparse[idx] = alloc_traits::allocate(allocator, sparse_page);
std::uninitialized_fill(sparse[idx], sparse[idx] + sparse_page, null);
}
return sparse[idx];
@@ -210,8 +211,8 @@ class basic_sparse_set {
for(size_type pos{}; pos < bucket; ++pos) {
if(sparse[pos]) {
std::destroy(sparse[pos], sparse[pos] + page_size);
alloc_traits::deallocate(allocator, sparse[pos], page_size);
std::destroy(sparse[pos], sparse[pos] + sparse_page);
alloc_traits::deallocate(allocator, sparse[pos], sparse_page);
}
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(sparse[pos]));
@@ -376,7 +377,7 @@ public:
* @return Extent of the sparse set.
*/
[[nodiscard]] size_type extent() const ENTT_NOEXCEPT {
return bucket * page_size;
return bucket * sparse_page;
}
/**

View File

@@ -51,6 +51,8 @@ template<typename Entity, typename Type, typename Allocator, typename>
class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
static_assert(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>, "The managed type must be at least move constructible and assignable");
static constexpr auto packed_page = ENTT_PACKED_PAGE;
using underlying_type = basic_sparse_set<Entity>;
using traits_type = entt_traits<Entity>;
@@ -172,23 +174,23 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
};
[[nodiscard]] static auto page(const std::size_t pos) ENTT_NOEXCEPT {
return pos / page_size;
return pos / packed_page;
}
[[nodiscard]] static auto offset(const std::size_t pos) ENTT_NOEXCEPT {
return pos & (page_size - 1);
return pos & (packed_page - 1);
}
void release_memory() {
if(packed) {
for(size_type pos{}; pos < bucket; ++pos) {
if(count) {
const auto sz = count > page_size ? page_size : count;
const auto sz = count > packed_page ? packed_page : count;
std::destroy(packed[pos], packed[pos] + sz);
count -= sz;
}
alloc_traits::deallocate(allocator, packed[pos], page_size);
alloc_traits::deallocate(allocator, packed[pos], packed_page);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
}
@@ -199,7 +201,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
void maybe_resize_packed(const std::size_t req) {
ENTT_ASSERT(!(req < count), "Invalid request");
if(const auto length = std::exchange(bucket, (req + page_size - 1u) / page_size); bucket != length) {
if(const auto length = std::exchange(bucket, (req + packed_page - 1u) / packed_page); bucket != length) {
const auto old = std::exchange(packed, bucket_alloc_traits::allocate(bucket_allocator, bucket));
if(bucket > length) {
@@ -209,7 +211,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
}
for(auto pos = length; pos < bucket; ++pos) {
bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), alloc_traits::allocate(allocator, page_size));
bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), alloc_traits::allocate(allocator, packed_page));
}
} else if(bucket < length) {
for(size_type pos{}; pos < bucket; ++pos) {
@@ -218,7 +220,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
}
for(auto pos = bucket; pos < length; ++pos) {
alloc_traits::deallocate(allocator, old[pos], page_size);
alloc_traits::deallocate(allocator, old[pos], packed_page);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
}
}
@@ -229,7 +231,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
template<typename... Args>
Type & push_back(Args &&... args) {
ENTT_ASSERT(count < (bucket * page_size), "No more space left");
ENTT_ASSERT(count < (bucket * packed_page), "No more space left");
auto &ref = packed[page(count)][offset(count)];
if constexpr(std::is_aggregate_v<value_type>) {
@@ -340,7 +342,7 @@ public:
void reserve(const size_type cap) {
underlying_type::reserve(cap);
if(cap > (bucket * page_size)) {
if(cap > (bucket * packed_page)) {
maybe_resize_packed(cap);
}
}
@@ -351,7 +353,7 @@ public:
* @return Capacity of the storage.
*/
[[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
return bucket * page_size;
return bucket * packed_page;
}
/*! @brief Requests the removal of unused capacity. */

View File

@@ -50,7 +50,7 @@ TEST(Helper, ToEntity) {
const auto entity = registry.create();
registry.emplace<int>(entity);
while(registry.size<int>() < (entt::page_size - 1u)) {
while(registry.size<int>() < (ENTT_PACKED_PAGE - 1u)) {
registry.emplace<int>(registry.create(), value);
}
@@ -64,15 +64,15 @@ TEST(Helper, ToEntity) {
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(other)), other);
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(next)), next);
ASSERT_EQ(&registry.get<int>(entity) + entt::page_size - 1u, &registry.get<int>(other));
ASSERT_NE(&registry.get<int>(entity) + entt::page_size, &registry.get<int>(next));
ASSERT_EQ(&registry.get<int>(entity) + ENTT_PACKED_PAGE - 1u, &registry.get<int>(other));
ASSERT_NE(&registry.get<int>(entity) + ENTT_PACKED_PAGE, &registry.get<int>(next));
registry.destroy(other);
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(entity)), entity);
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(next)), next);
ASSERT_EQ(&registry.get<int>(entity) + entt::page_size - 1u, &registry.get<int>(next));
ASSERT_EQ(&registry.get<int>(entity) + ENTT_PACKED_PAGE - 1u, &registry.get<int>(next));
ASSERT_EQ(entt::to_entity(registry, 42), null);
ASSERT_EQ(entt::to_entity(registry, value), null);

View File

@@ -154,8 +154,8 @@ TEST(Registry, Functionalities) {
ASSERT_TRUE(registry.empty());
ASSERT_EQ(registry.capacity(), 42u);
ASSERT_EQ(registry.capacity<int>(), entt::page_size);
ASSERT_EQ(registry.capacity<char>(), entt::page_size);
ASSERT_EQ(registry.capacity<int>(), ENTT_PACKED_PAGE);
ASSERT_EQ(registry.capacity<char>(), ENTT_PACKED_PAGE);
ASSERT_EQ(registry.size<int>(), 0u);
ASSERT_EQ(registry.size<char>(), 0u);
ASSERT_TRUE((registry.empty<int, char>()));
@@ -295,8 +295,8 @@ TEST(Registry, Functionalities) {
ASSERT_EQ(registry.size<char>(), 0u);
ASSERT_TRUE(registry.empty<int>());
ASSERT_EQ(registry.capacity<int>(), entt::page_size);
ASSERT_EQ(registry.capacity<char>(), entt::page_size);
ASSERT_EQ(registry.capacity<int>(), ENTT_PACKED_PAGE);
ASSERT_EQ(registry.capacity<char>(), ENTT_PACKED_PAGE);
registry.shrink_to_fit<int, char>();

View File

@@ -96,34 +96,34 @@ TEST(SparseSet, Pagination) {
ASSERT_EQ(set.extent(), 0u);
set.emplace(entt::entity{entt::page_size-1u});
set.emplace(entt::entity{ENTT_SPARSE_PAGE-1u});
ASSERT_EQ(set.extent(), entt::page_size);
ASSERT_TRUE(set.contains(entt::entity{entt::page_size-1u}));
ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE);
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
set.emplace(entt::entity{entt::page_size});
set.emplace(entt::entity{ENTT_SPARSE_PAGE});
ASSERT_EQ(set.extent(), 2 * entt::page_size);
ASSERT_TRUE(set.contains(entt::entity{entt::page_size-1u}));
ASSERT_TRUE(set.contains(entt::entity{entt::page_size}));
ASSERT_FALSE(set.contains(entt::entity{entt::page_size+1u}));
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE+1u}));
set.erase(entt::entity{entt::page_size-1u});
set.erase(entt::entity{ENTT_SPARSE_PAGE-1u});
ASSERT_EQ(set.extent(), 2 * entt::page_size);
ASSERT_FALSE(set.contains(entt::entity{entt::page_size-1u}));
ASSERT_TRUE(set.contains(entt::entity{entt::page_size}));
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
set.shrink_to_fit();
set.erase(entt::entity{entt::page_size});
set.erase(entt::entity{ENTT_SPARSE_PAGE});
ASSERT_EQ(set.extent(), 2 * entt::page_size);
ASSERT_FALSE(set.contains(entt::entity{entt::page_size-1u}));
ASSERT_FALSE(set.contains(entt::entity{entt::page_size}));
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
set.shrink_to_fit();
ASSERT_EQ(set.extent(), 2 * entt::page_size);
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
}
TEST(SparseSet, Insert) {

View File

@@ -40,7 +40,7 @@ TEST(Storage, Functionalities) {
pool.reserve(42);
ASSERT_EQ(pool.capacity(), entt::page_size);
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
ASSERT_TRUE(pool.empty());
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(std::as_const(pool).begin(), std::as_const(pool).end());
@@ -50,7 +50,7 @@ TEST(Storage, Functionalities) {
pool.reserve(0);
ASSERT_EQ(pool.capacity(), entt::page_size);
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
ASSERT_TRUE(pool.empty());
pool.emplace(entt::entity{41}, 3);
@@ -85,7 +85,7 @@ TEST(Storage, Functionalities) {
ASSERT_FALSE(pool.contains(entt::entity{0}));
ASSERT_FALSE(pool.contains(entt::entity{41}));
ASSERT_EQ(pool.capacity(), entt::page_size);
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
pool.shrink_to_fit();
@@ -223,24 +223,24 @@ TEST(Storage, Remove) {
TEST(Storage, ShrinkToFit) {
entt::storage<int> pool;
for(std::size_t next{}; next < entt::page_size; ++next) {
for(std::size_t next{}; next < ENTT_PACKED_PAGE; ++next) {
pool.emplace(entt::entity(next));
}
pool.emplace(entt::entity{entt::page_size});
pool.erase(entt::entity{entt::page_size});
pool.emplace(entt::entity{ENTT_PACKED_PAGE});
pool.erase(entt::entity{ENTT_PACKED_PAGE});
ASSERT_EQ(pool.capacity(), 2 * entt::page_size);
ASSERT_EQ(pool.size(), entt::page_size);
ASSERT_EQ(pool.capacity(), 2 * ENTT_PACKED_PAGE);
ASSERT_EQ(pool.size(), ENTT_PACKED_PAGE);
pool.shrink_to_fit();
ASSERT_EQ(pool.capacity(), entt::page_size);
ASSERT_EQ(pool.size(), entt::page_size);
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
ASSERT_EQ(pool.size(), ENTT_PACKED_PAGE);
pool.clear();
ASSERT_EQ(pool.capacity(), entt::page_size);
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
ASSERT_EQ(pool.size(), 0u);
pool.shrink_to_fit();
@@ -691,12 +691,12 @@ TEST(Storage, CanModifyDuringIteration) {
entt::storage<int> pool;
pool.emplace(entt::entity{0}, 42);
ASSERT_EQ(pool.capacity(), entt::page_size);
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
const auto it = pool.cbegin();
pool.reserve(entt::page_size + 1u);
pool.reserve(ENTT_PACKED_PAGE + 1u);
ASSERT_EQ(pool.capacity(), 2 * entt::page_size);
ASSERT_EQ(pool.capacity(), 2 * ENTT_PACKED_PAGE);
// this should crash with asan enabled if we break the constraint
const auto entity = *it;