diff --git a/src/entt/entity/sparse_set.hpp b/src/entt/entity/sparse_set.hpp index 03afcf76e..a601df1ad 100644 --- a/src/entt/entity/sparse_set.hpp +++ b/src/entt/entity/sparse_set.hpp @@ -6,9 +6,9 @@ #include #include #include +#include #include "../config/config.h" #include "../core/algorithm.hpp" -#include "../core/compressed_pair.hpp" #include "../core/memory.hpp" #include "entity.hpp" #include "fwd.hpp" @@ -22,17 +22,17 @@ namespace entt { namespace internal { -template +template struct sparse_set_iterator final { - using value_type = typename Traits::value_type; - using pointer = typename Traits::pointer; - using reference = typename Traits::reference; - using difference_type = typename Traits::difference_type; + using value_type = typename Container::value_type; + using pointer = typename Container::const_pointer; + using reference = typename Container::const_reference; + using difference_type = typename Container::difference_type; using iterator_category = std::random_access_iterator_tag; sparse_set_iterator() ENTT_NOEXCEPT = default; - sparse_set_iterator(const pointer *ref, const difference_type idx) ENTT_NOEXCEPT + sparse_set_iterator(const Container *ref, const difference_type idx) ENTT_NOEXCEPT : packed{ref}, index{idx} {} @@ -106,7 +106,7 @@ struct sparse_set_iterator final { [[nodiscard]] pointer operator->() const { const auto pos = index - 1; - return (*packed) + pos; + return packed->data() + pos; } [[nodiscard]] reference operator*() const { @@ -114,7 +114,7 @@ struct sparse_set_iterator final { } private: - const pointer *packed; + const Container *packed; difference_type index; }; @@ -158,26 +158,21 @@ enum class deletion_policy : std::uint8_t { */ template class basic_sparse_set { - static constexpr auto growth_factor_v = 1.5; static constexpr auto sparse_page_v = ENTT_SPARSE_PAGE; using allocator_traits = std::allocator_traits; - using alloc = typename allocator_traits::template rebind_alloc; using alloc_traits = typename std::allocator_traits; - using alloc_const_pointer = typename alloc_traits::const_pointer; - using alloc_pointer = typename alloc_traits::pointer; - - using alloc_ptr = typename allocator_traits::template rebind_alloc; - using alloc_ptr_traits = typename std::allocator_traits; - using alloc_ptr_pointer = typename alloc_ptr_traits::pointer; + using alloc_page = typename allocator_traits::template rebind_alloc; using entity_traits = entt_traits; + using sparse_container_type = std::vector; + using packed_container_type = std::vector; [[nodiscard]] auto sparse_ptr(const Entity entt) const { const auto pos = static_cast(entity_traits::to_entity(entt)); const auto page = pos / sparse_page_v; - return (page < bucket && sparse[page]) ? (sparse[page] + fast_mod(pos)) : alloc_pointer{}; + return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos)) : nullptr; } [[nodiscard]] auto &sparse_ref(const Entity entt) const { @@ -186,40 +181,13 @@ class basic_sparse_set { return sparse[pos / sparse_page_v][fast_mod(pos)]; } - void resize_packed(const std::size_t req) { - ENTT_ASSERT((req != reserved.second()) && !(req < count), "Invalid request"); - const auto mem = alloc_traits::allocate(reserved.first(), req); - - std::uninitialized_fill(mem + count, mem + req, tombstone); - - if(packed) { - std::uninitialized_copy(packed, packed + count, mem); - std::destroy(packed, packed + reserved.second()); - alloc_traits::deallocate(reserved.first(), packed, reserved.second()); - } - - packed = mem; - reserved.second() = req; - } - - void release_memory() { - if(packed) { - std::destroy(packed, packed + reserved.second()); - alloc_traits::deallocate(reserved.first(), packed, reserved.second()); - } - - if(sparse) { - for(size_type pos{}; pos < bucket; ++pos) { - if(sparse[pos]) { - std::destroy(sparse[pos], sparse[pos] + sparse_page_v); - alloc_traits::deallocate(reserved.first(), sparse[pos], sparse_page_v); - } - - std::destroy_at(std::addressof(sparse[pos])); + void release_sparse_pages() { + for(size_type pos{}, last = sparse.size(); pos < last; ++pos) { + if(sparse[pos]) { + std::destroy(sparse[pos], sparse[pos] + sparse_page_v); + alloc_traits::deallocate(packed.get_allocator(), sparse[pos], sparse_page_v); + sparse[pos] = nullptr; } - - alloc_ptr allocator_ptr{reserved.first()}; - alloc_ptr_traits::deallocate(allocator_ptr, sparse, bucket); } } @@ -241,15 +209,15 @@ protected: auto &ref = sparse_ref(entt); const auto pos = static_cast(entity_traits::to_entity(ref)); ENTT_ASSERT(packed[pos] == entt, "Invalid identifier"); - auto &last = packed[--count]; - packed[pos] = last; - auto &elem = sparse_ref(last); + packed[pos] = packed.back(); + auto &elem = sparse_ref(packed.back()); elem = entity_traits::combine(entity_traits::to_integral(ref), entity_traits::to_integral(elem)); // lazy self-assignment guard ref = null; // unnecessary but it helps to detect nasty bugs - ENTT_ASSERT((last = tombstone, true), ""); + ENTT_ASSERT((packed.back() = tombstone, true), ""); + packed.pop_back(); } /** @@ -274,25 +242,12 @@ protected: const auto pos = static_cast(entity_traits::to_entity(entt)); const auto page = pos / sparse_page_v; - if(!(page < bucket)) { - const size_type sz = page + 1u; - alloc_ptr allocator_ptr{reserved.first()}; - const auto mem = alloc_ptr_traits::allocate(allocator_ptr, sz); - - std::uninitialized_value_construct(mem + bucket, mem + sz); - - if(sparse) { - std::uninitialized_copy(sparse, sparse + bucket, mem); - std::destroy(sparse, sparse + bucket); - alloc_ptr_traits::deallocate(allocator_ptr, sparse, bucket); - } - - sparse = mem; - bucket = sz; + if(!(page < sparse.size())) { + sparse.resize(page + 1u, nullptr); } if(!sparse[page]) { - sparse[page] = alloc_traits::allocate(reserved.first(), sparse_page_v); + sparse[page] = alloc_traits::allocate(packed.get_allocator(), sparse_page_v); std::uninitialized_fill(sparse[page], sparse[page] + sparse_page_v, null); } @@ -300,13 +255,8 @@ protected: ENTT_ASSERT(entity_traits::to_version(elem) == entity_traits::to_version(tombstone), "Slot not available"); if(free_list == null) { - if(count == reserved.second()) { - const size_type sz = static_cast(reserved.second() * growth_factor_v); - resize_packed(sz + !(sz > reserved.second())); - } - - elem = entity_traits::combine(static_cast(count), entity_traits::to_integral(entt)); - packed[count++] = entt; + elem = entity_traits::combine(static_cast(packed.size()), entity_traits::to_integral(entt)); + packed.push_back(entt); } else { elem = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt)); free_list = std::exchange(packed[static_cast(entity_traits::to_entity(free_list))], entt); @@ -321,11 +271,11 @@ public: /*! @brief Underlying version type. */ using version_type = typename entity_traits::version_type; /*! @brief Unsigned integer type. */ - using size_type = std::size_t; + using size_type = typename packed_container_type::size_type; /*! @brief Pointer type to contained entities. */ - using pointer = alloc_const_pointer; + using pointer = typename packed_container_type::const_pointer; /*! @brief Random access iterator type. */ - using iterator = internal::sparse_set_iterator>; + using iterator = internal::sparse_set_iterator; /*! @brief Reverse iterator type. */ using reverse_iterator = std::reverse_iterator; @@ -346,12 +296,9 @@ public: * @param allocator The allocator to use (possibly default-constructed). */ explicit basic_sparse_set(deletion_policy pol, const allocator_type &allocator = {}) - : reserved{allocator, size_type{}}, - sparse{}, - packed{}, + : sparse{allocator}, + packed{allocator}, udata{}, - count{}, - bucket{}, free_list{tombstone}, mode{pol} {} @@ -360,12 +307,9 @@ public: * @param other The instance to move from. */ basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT - : reserved{std::move(other.reserved.first()), std::exchange(other.reserved.second(), size_type{})}, - sparse{std::exchange(other.sparse, alloc_ptr_pointer{})}, - packed{std::exchange(other.packed, alloc_pointer{})}, + : sparse{std::move(other.sparse)}, + packed{std::move(other.packed)}, udata{std::exchange(other.udata, nullptr)}, - count{std::exchange(other.count, size_type{})}, - bucket{std::exchange(other.bucket, size_type{})}, free_list{std::exchange(other.free_list, tombstone)}, mode{other.mode} {} @@ -375,20 +319,17 @@ public: * @param allocator The allocator to use. */ basic_sparse_set(basic_sparse_set &&other, const allocator_type &allocator) ENTT_NOEXCEPT - : reserved{allocator, std::exchange(other.reserved.second(), size_type{})}, - sparse{std::exchange(other.sparse, alloc_ptr_pointer{})}, - packed{std::exchange(other.packed, alloc_pointer{})}, + : sparse{std::move(other.sparse), allocator}, + packed{std::move(other.packed), allocator}, udata{std::exchange(other.udata, nullptr)}, - count{std::exchange(other.count, size_type{})}, - bucket{std::exchange(other.bucket, size_type{})}, free_list{std::exchange(other.free_list, tombstone)}, mode{other.mode} { - ENTT_ASSERT(alloc_traits::is_always_equal::value || reserved.first() == other.reserved.first(), "Copying a sparse set is not allowed"); + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed"); } /*! @brief Default destructor. */ virtual ~basic_sparse_set() { - release_memory(); + release_sparse_pages(); } /** @@ -397,15 +338,11 @@ public: * @return This sparse set. */ basic_sparse_set &operator=(basic_sparse_set &&other) ENTT_NOEXCEPT { - release_memory(); - propagate_on_container_move_assignment(reserved.first(), other.reserved.first()); - ENTT_ASSERT(alloc_traits::is_always_equal::value || reserved.first() == other.reserved.first(), "Copying a sparse set is not allowed"); - reserved.second() = std::exchange(other.reserved.second(), size_type{}); - sparse = std::exchange(other.sparse, alloc_ptr_pointer{}); - packed = std::exchange(other.packed, alloc_pointer{}); + release_sparse_pages(); + sparse = std::move(other.sparse); + packed = std::move(other.packed); + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed"); udata = std::exchange(other.udata, nullptr); - count = std::exchange(other.count, size_type{}); - bucket = std::exchange(other.bucket, size_type{}); free_list = std::exchange(other.free_list, tombstone); mode = other.mode; return *this; @@ -418,13 +355,9 @@ public: void swap(basic_sparse_set &other) { using std::swap; swap_contents(other); - propagate_on_container_swap(reserved.first(), other.reserved.first()); - swap(reserved.second(), other.reserved.second()); swap(sparse, other.sparse); swap(packed, other.packed); swap(udata, other.udata); - swap(count, other.count); - swap(bucket, other.bucket); swap(free_list, other.free_list); swap(mode, other.mode); } @@ -434,7 +367,7 @@ public: * @return The associated allocator. */ [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { - return allocator_type{reserved.first()}; + return packed.get_allocator(); } /** @@ -450,7 +383,7 @@ public: * @return The next slot available for insertion. */ [[nodiscard]] size_type slot() const ENTT_NOEXCEPT { - return free_list == null ? count : static_cast(entity_traits::to_entity(free_list)); + return free_list == null ? packed.size() : static_cast(entity_traits::to_entity(free_list)); } /** @@ -462,9 +395,7 @@ public: * @param cap Desired capacity. */ virtual void reserve(const size_type cap) { - if(cap > reserved.second()) { - resize_packed(cap); - } + packed.reserve(cap); } /** @@ -473,14 +404,12 @@ public: * @return Capacity of the sparse set. */ [[nodiscard]] virtual size_type capacity() const ENTT_NOEXCEPT { - return reserved.second(); + return packed.capacity(); } /*! @brief Requests the removal of unused capacity. */ virtual void shrink_to_fit() { - if(count < reserved.second()) { - resize_packed(count); - } + packed.shrink_to_fit(); } /** @@ -494,7 +423,7 @@ public: * @return Extent of the sparse set. */ [[nodiscard]] size_type extent() const ENTT_NOEXCEPT { - return bucket * sparse_page_v; + return sparse.size() * sparse_page_v; } /** @@ -508,7 +437,7 @@ public: * @return Number of elements. */ [[nodiscard]] size_type size() const ENTT_NOEXCEPT { - return count; + return packed.size(); } /** @@ -516,7 +445,7 @@ public: * @return True if the sparse set is empty, false otherwise. */ [[nodiscard]] bool empty() const ENTT_NOEXCEPT { - return (count == size_type{}); + return packed.empty(); } /** @@ -524,7 +453,7 @@ public: * @return A pointer to the internal packed array. */ [[nodiscard]] pointer data() const ENTT_NOEXCEPT { - return packed; + return packed.data(); } /** @@ -537,8 +466,8 @@ public: * @return An iterator to the first entity of the sparse set. */ [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { - const auto pos = static_cast(count); - return iterator{std::addressof(packed), pos}; + const auto pos = static_cast(packed.size()); + return iterator{&packed, pos}; } /** @@ -552,7 +481,7 @@ public: * set. */ [[nodiscard]] iterator end() const ENTT_NOEXCEPT { - return iterator{std::addressof(packed), {}}; + return iterator{&packed, {}}; } /** @@ -643,7 +572,7 @@ public: * @return The entity at specified location if any, a null entity otherwise. */ [[nodiscard]] entity_type at(const size_type pos) const ENTT_NOEXCEPT { - return pos < count ? packed[pos] : null; + return pos < packed.size() ? packed[pos] : null; } /** @@ -652,7 +581,7 @@ public: * @return The entity at specified location. */ [[nodiscard]] entity_type operator[](const size_type pos) const ENTT_NOEXCEPT { - ENTT_ASSERT(pos < count, "Position is out of bounds"); + ENTT_ASSERT(pos < packed.size(), "Position is out of bounds"); return packed[pos]; } @@ -689,7 +618,7 @@ public: emplace(*first, ud); } - reserve(count + std::distance(first, last)); + reserve(packed.size() + std::distance(first, last)); for(; first != last; ++first) { emplace(*first, ud); @@ -760,7 +689,7 @@ public: /*! @brief Removes all tombstones from the packed array of a sparse set. */ void compact() { - size_type next = count; + size_type next = packed.size(); for(; next && packed[next - 1u] == tombstone; --next) {} for(auto *it = &free_list; *it != null && next; it = std::addressof(packed[entity_traits::to_entity(*it)])) { @@ -776,7 +705,7 @@ public: } free_list = tombstone; - count = next; + packed.resize(next); } /** @@ -840,11 +769,10 @@ public: */ template void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&...args) { - // basic no-leak guarantee (with invalid state) if sorting throws - ENTT_ASSERT(!(length > count), "Length exceeds the number of elements"); + ENTT_ASSERT(!(length > packed.size()), "Length exceeds the number of elements"); ENTT_ASSERT(free_list == null, "Partial sorting with tombstones is not supported"); - algo(std::make_reverse_iterator(packed + length), std::make_reverse_iterator(packed), std::move(compare), std::forward(args)...); + algo(packed.rend() - length, packed.rend(), std::move(compare), std::forward(args)...); for(size_type pos{}; pos < length; ++pos) { auto curr = pos; @@ -877,7 +805,7 @@ public: template void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { compact(); - sort_n(count, std::move(compare), std::move(algo), std::forward(args)...); + sort_n(packed.size(), std::move(compare), std::move(algo), std::forward(args)...); } /** @@ -901,7 +829,7 @@ public: const auto to = other.end(); auto from = other.begin(); - for(size_type pos = count - 1; pos && from != to; ++from) { + for(size_type pos = packed.size() - 1; pos && from != to; ++from) { if(contains(*from)) { if(*from != packed[pos]) { // basic no-leak guarantee (with invalid state) if swapping throws @@ -946,12 +874,9 @@ public: } private: - compressed_pair reserved; - alloc_ptr_pointer sparse; - alloc_pointer packed; + sparse_container_type sparse; + packed_container_type packed; void *udata; - size_type count; - size_type bucket; entity_type free_list; deletion_policy mode; };