From 755699c31be532ba94b5c824db5d65ffd5bfc85f Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Mon, 24 May 2021 23:29:55 +0200 Subject: [PATCH] config: * removed entt::page_size * renamed ENTT_PAGE_SIZE to ENTT_SPARSE_PAGE * added ENTT_PACKED_PAGE (page size for component arrays) --- docs/md/config.md | 18 +++++++++++++---- src/entt/config/config.h | 13 ++++++++++--- src/entt/core/fwd.hpp | 3 --- src/entt/entity/helper.hpp | 4 ++-- src/entt/entity/sparse_set.hpp | 15 ++++++++------- src/entt/entity/storage.hpp | 22 +++++++++++---------- test/entt/entity/helper.cpp | 8 ++++---- test/entt/entity/registry.cpp | 8 ++++---- test/entt/entity/sparse_set.cpp | 34 ++++++++++++++++----------------- test/entt/entity/storage.cpp | 28 +++++++++++++-------------- 10 files changed, 85 insertions(+), 68 deletions(-) diff --git a/docs/md/config.md b/docs/md/config.md index 6ba7ab671..9915f7a17 100644 --- a/docs/md/config.md +++ b/docs/md/config.md @@ -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.
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.
+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.
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.
+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 diff --git a/src/entt/config/config.h b/src/entt/config/config.h index d272144dd..c5f6c2a5d 100644 --- a/src/entt/config/config.h +++ b/src/entt/config/config.h @@ -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 diff --git a/src/entt/core/fwd.hpp b/src/entt/core/fwd.hpp index 826e3e3ea..193aad86a 100644 --- a/src/entt/core/fwd.hpp +++ b/src/entt/core/fwd.hpp @@ -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<>; diff --git a/src/entt/entity/helper.hpp b/src/entt/entity/helper.hpp index 1e65cf653..c7e437d1b 100644 --- a/src/entt/entity/helper.hpp +++ b/src/entt/entity/helper.hpp @@ -152,8 +152,8 @@ Entity to_entity(const basic_registry ®, const Component &instance) { const auto view = reg.template view(); 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(*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(*it))); dist >= 0 && dist < ENTT_PACKED_PAGE) { return *(it + dist); } } diff --git a/src/entt/entity/sparse_set.hpp b/src/entt/entity/sparse_set.hpp index ff595888e..d7b37cbf8 100644 --- a/src/entt/entity/sparse_set.hpp +++ b/src/entt/entity/sparse_set.hpp @@ -43,6 +43,7 @@ namespace entt { template class basic_sparse_set { static constexpr auto growth_factor = 1.5; + static constexpr auto sparse_page = ENTT_SPARSE_PAGE; using traits_type = entt_traits; @@ -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; } /** diff --git a/src/entt/entity/storage.hpp b/src/entt/entity/storage.hpp index 0f8402e73..a95c6b142 100644 --- a/src/entt/entity/storage.hpp +++ b/src/entt/entity/storage.hpp @@ -51,6 +51,8 @@ template class basic_storage: public basic_sparse_set::template rebind_alloc> { static_assert(std::is_move_constructible_v && std::is_move_assignable_v, "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; using traits_type = entt_traits; @@ -172,23 +174,23 @@ class basic_storage: public basic_sparse_set 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 length) { @@ -209,7 +211,7 @@ class basic_storage: public basic_sparse_set 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) { @@ -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. */ diff --git a/test/entt/entity/helper.cpp b/test/entt/entity/helper.cpp index a5976ba21..395f3c911 100644 --- a/test/entt/entity/helper.cpp +++ b/test/entt/entity/helper.cpp @@ -50,7 +50,7 @@ TEST(Helper, ToEntity) { const auto entity = registry.create(); registry.emplace(entity); - while(registry.size() < (entt::page_size - 1u)) { + while(registry.size() < (ENTT_PACKED_PAGE - 1u)) { registry.emplace(registry.create(), value); } @@ -64,15 +64,15 @@ TEST(Helper, ToEntity) { ASSERT_EQ(entt::to_entity(registry, registry.get(other)), other); ASSERT_EQ(entt::to_entity(registry, registry.get(next)), next); - ASSERT_EQ(®istry.get(entity) + entt::page_size - 1u, ®istry.get(other)); - ASSERT_NE(®istry.get(entity) + entt::page_size, ®istry.get(next)); + ASSERT_EQ(®istry.get(entity) + ENTT_PACKED_PAGE - 1u, ®istry.get(other)); + ASSERT_NE(®istry.get(entity) + ENTT_PACKED_PAGE, ®istry.get(next)); registry.destroy(other); ASSERT_EQ(entt::to_entity(registry, registry.get(entity)), entity); ASSERT_EQ(entt::to_entity(registry, registry.get(next)), next); - ASSERT_EQ(®istry.get(entity) + entt::page_size - 1u, ®istry.get(next)); + ASSERT_EQ(®istry.get(entity) + ENTT_PACKED_PAGE - 1u, ®istry.get(next)); ASSERT_EQ(entt::to_entity(registry, 42), null); ASSERT_EQ(entt::to_entity(registry, value), null); diff --git a/test/entt/entity/registry.cpp b/test/entt/entity/registry.cpp index 28c2e84e2..f9668e94c 100644 --- a/test/entt/entity/registry.cpp +++ b/test/entt/entity/registry.cpp @@ -154,8 +154,8 @@ TEST(Registry, Functionalities) { ASSERT_TRUE(registry.empty()); ASSERT_EQ(registry.capacity(), 42u); - ASSERT_EQ(registry.capacity(), entt::page_size); - ASSERT_EQ(registry.capacity(), entt::page_size); + ASSERT_EQ(registry.capacity(), ENTT_PACKED_PAGE); + ASSERT_EQ(registry.capacity(), ENTT_PACKED_PAGE); ASSERT_EQ(registry.size(), 0u); ASSERT_EQ(registry.size(), 0u); ASSERT_TRUE((registry.empty())); @@ -295,8 +295,8 @@ TEST(Registry, Functionalities) { ASSERT_EQ(registry.size(), 0u); ASSERT_TRUE(registry.empty()); - ASSERT_EQ(registry.capacity(), entt::page_size); - ASSERT_EQ(registry.capacity(), entt::page_size); + ASSERT_EQ(registry.capacity(), ENTT_PACKED_PAGE); + ASSERT_EQ(registry.capacity(), ENTT_PACKED_PAGE); registry.shrink_to_fit(); diff --git a/test/entt/entity/sparse_set.cpp b/test/entt/entity/sparse_set.cpp index ac2f5bc4f..8253bdf98 100644 --- a/test/entt/entity/sparse_set.cpp +++ b/test/entt/entity/sparse_set.cpp @@ -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) { diff --git a/test/entt/entity/storage.cpp b/test/entt/entity/storage.cpp index edf5e3765..664213c22 100644 --- a/test/entt/entity/storage.cpp +++ b/test/entt/entity/storage.cpp @@ -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 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 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;