config:
* 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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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<>;
|
||||
|
||||
|
||||
@@ -152,8 +152,8 @@ Entity to_entity(const basic_registry<Entity> ®, 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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(®istry.get<int>(entity) + entt::page_size - 1u, ®istry.get<int>(other));
|
||||
ASSERT_NE(®istry.get<int>(entity) + entt::page_size, ®istry.get<int>(next));
|
||||
ASSERT_EQ(®istry.get<int>(entity) + ENTT_PACKED_PAGE - 1u, ®istry.get<int>(other));
|
||||
ASSERT_NE(®istry.get<int>(entity) + ENTT_PACKED_PAGE, ®istry.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(®istry.get<int>(entity) + entt::page_size - 1u, ®istry.get<int>(next));
|
||||
ASSERT_EQ(®istry.get<int>(entity) + ENTT_PACKED_PAGE - 1u, ®istry.get<int>(next));
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, 42), null);
|
||||
ASSERT_EQ(entt::to_entity(registry, value), null);
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user