#ifndef ENTT_ENTITY_STORAGE_HPP #define ENTT_ENTITY_STORAGE_HPP #include #include #include #include #include #include #include #include "../config/config.h" #include "../core/compressed_pair.hpp" #include "../core/iterator.hpp" #include "../core/memory.hpp" #include "../core/type_info.hpp" #include "component.hpp" #include "entity.hpp" #include "fwd.hpp" #include "sigh_storage_mixin.hpp" #include "sparse_set.hpp" namespace entt { /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ namespace internal { template class storage_iterator final { friend storage_iterator; using container_type = std::remove_const_t; using alloc_traits = std::allocator_traits; using comp_traits = component_traits; using iterator_traits = std::iterator_traits, typename alloc_traits::template rebind_traits::element_type>::const_pointer, typename alloc_traits::template rebind_traits::element_type>::pointer>>; public: using value_type = typename iterator_traits::value_type; using pointer = typename iterator_traits::pointer; using reference = typename iterator_traits::reference; using difference_type = typename iterator_traits::difference_type; using iterator_category = std::random_access_iterator_tag; storage_iterator() ENTT_NOEXCEPT = default; storage_iterator(Container *ref, difference_type idx) ENTT_NOEXCEPT : packed{ref}, offset{idx} {} template, typename = std::enable_if_t> storage_iterator(const storage_iterator> &other) ENTT_NOEXCEPT : packed{other.packed}, offset{other.offset} {} storage_iterator &operator++() ENTT_NOEXCEPT { return --offset, *this; } storage_iterator operator++(int) ENTT_NOEXCEPT { storage_iterator orig = *this; return ++(*this), orig; } storage_iterator &operator--() ENTT_NOEXCEPT { return ++offset, *this; } storage_iterator operator--(int) ENTT_NOEXCEPT { storage_iterator orig = *this; return operator--(), orig; } storage_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { offset -= value; return *this; } storage_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { storage_iterator copy = *this; return (copy += value); } storage_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { return (*this += -value); } storage_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { return (*this + -value); } [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { const auto pos = index() - value; return (*packed)[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)]; } [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { const auto pos = index(); return (*packed)[pos / comp_traits::page_size] + fast_mod(pos, comp_traits::page_size); } [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { return *operator->(); } [[nodiscard]] difference_type index() const ENTT_NOEXCEPT { return offset - 1; } private: Container *packed; difference_type offset; }; template [[nodiscard]] std::ptrdiff_t operator-(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { return rhs.index() - lhs.index(); } template [[nodiscard]] bool operator==(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { return lhs.index() == rhs.index(); } template [[nodiscard]] bool operator!=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } template [[nodiscard]] bool operator<(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { return lhs.index() > rhs.index(); } template [[nodiscard]] bool operator>(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { return lhs.index() < rhs.index(); } template [[nodiscard]] bool operator<=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { return !(lhs > rhs); } template [[nodiscard]] bool operator>=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { return !(lhs < rhs); } template class extended_storage_iterator final { template friend class extended_storage_iterator; public: using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::forward_as_tuple(*std::declval()...))); using pointer = input_iterator_pointer; using reference = value_type; using difference_type = std::ptrdiff_t; using iterator_category = std::input_iterator_tag; extended_storage_iterator() = default; extended_storage_iterator(It base, Other... other) : it{base, other...} {} template && ...) && (std::is_constructible_v && ...)>> extended_storage_iterator(const extended_storage_iterator &other) : it{other.it} {} extended_storage_iterator &operator++() ENTT_NOEXCEPT { return ++std::get(it), (++std::get(it), ...), *this; } extended_storage_iterator operator++(int) ENTT_NOEXCEPT { extended_storage_iterator orig = *this; return ++(*this), orig; } [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { return operator*(); } [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { return {*std::get(it), *std::get(it)...}; } template friend bool operator==(const extended_storage_iterator &, const extended_storage_iterator &) ENTT_NOEXCEPT; private: std::tuple it; }; template [[nodiscard]] bool operator==(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) ENTT_NOEXCEPT { return std::get<0>(lhs.it) == std::get<0>(rhs.it); } template [[nodiscard]] bool operator!=(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } } // namespace internal /** * Internal details not to be documented. * @endcond */ /** * @brief Basic storage implementation. * * Internal data structures arrange elements to maximize performance. There are * no guarantees that objects are returned in the insertion order when iterate * a storage. Do not make assumption on the order in any case. * * @warning * Empty types aren't explicitly instantiated. Therefore, many of the functions * normally available for non-empty types will not be available for empty ones. * * @tparam Entity A valid entity type (see entt_traits for more details). * @tparam Type Type of objects assigned to the entities. * @tparam Allocator Type of allocator used to manage memory and elements. */ template class basic_storage: public basic_sparse_set::template rebind_alloc> { static_assert(std::is_move_constructible_v && std::is_move_assignable_v, "The type must be at least move constructible/assignable"); using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); using underlying_type = basic_sparse_set>; using container_type = std::vector>; using comp_traits = component_traits; [[nodiscard]] auto &element_at(const std::size_t pos) const { return packed.first()[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)]; } auto assure_at_least(const std::size_t pos) { auto &&container = packed.first(); const auto idx = pos / comp_traits::page_size; if(!(idx < container.size())) { auto curr = container.size(); container.resize(idx + 1u, nullptr); ENTT_TRY { for(const auto last = container.size(); curr < last; ++curr) { container[curr] = alloc_traits::allocate(packed.second(), comp_traits::page_size); } } ENTT_CATCH { container.resize(curr); ENTT_THROW; } } return container[idx] + fast_mod(pos, comp_traits::page_size); } template auto emplace_element(const Entity entt, const bool force_back, Args &&...args) { const auto it = base_type::try_emplace(entt, force_back); ENTT_TRY { auto elem = assure_at_least(static_cast(it.index())); entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward(args)...); } ENTT_CATCH { if constexpr(comp_traits::in_place_delete) { base_type::in_place_pop(it, it + 1u); } else { base_type::swap_and_pop(it, it + 1u); } ENTT_THROW; } return it; } void shrink_to_size(const std::size_t sz) { for(auto pos = sz, length = base_type::size(); pos < length; ++pos) { if constexpr(comp_traits::in_place_delete) { if(base_type::at(pos) != tombstone) { std::destroy_at(std::addressof(element_at(pos))); } } else { std::destroy_at(std::addressof(element_at(pos))); } } auto &&container = packed.first(); auto page_allocator{packed.second()}; const auto from = (sz + comp_traits::page_size - 1u) / comp_traits::page_size; for(auto pos = from, last = container.size(); pos < last; ++pos) { alloc_traits::deallocate(page_allocator, container[pos], comp_traits::page_size); } container.resize(from); } private: const void *get_at(const std::size_t pos) const final { return std::addressof(element_at(pos)); } void swap_at(const std::size_t lhs, const std::size_t rhs) final { using std::swap; swap(element_at(lhs), element_at(rhs)); } void move_element(const std::size_t from, const std::size_t to) final { auto &elem = element_at(from); entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), packed.second(), std::move(elem)); std::destroy_at(std::addressof(elem)); } protected: /** * @brief Erases elements from a storage. * @param first An iterator to the first element to erase. * @param last An iterator past the last element to erase. */ void swap_and_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override { for(; first != last; ++first) { // cannot use first::index() because it would break with cross iterators const auto pos = base_type::index(*first); auto &elem = element_at(base_type::size() - 1u); // destroying on exit allows reentrant destructors [[maybe_unused]] auto unused = std::exchange(element_at(pos), std::move(elem)); std::destroy_at(std::addressof(elem)); base_type::swap_and_pop(first, first + 1u); } } /** * @brief Erases elements from a storage. * @param first An iterator to the first element to erase. * @param last An iterator past the last element to erase. */ void in_place_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override { for(; first != last; ++first) { // cannot use first::index() because it would break with cross iterators const auto pos = base_type::index(*first); base_type::in_place_pop(first, first + 1u); std::destroy_at(std::addressof(element_at(pos))); } } /** * @brief Assigns an entity to a storage. * @param entt A valid identifier. * @param value Optional opaque value. * @param force_back Force back insertion. * @return Iterator pointing to the emplaced element. */ typename underlying_type::basic_iterator try_emplace([[maybe_unused]] const Entity entt, const bool force_back, const void *value) override { if(value) { if constexpr(std::is_copy_constructible_v) { return emplace_element(entt, force_back, *static_cast(value)); } else { return base_type::end(); } } else { if constexpr(std::is_default_constructible_v) { return emplace_element(entt, force_back); } else { return base_type::end(); } } } public: /*! @brief Base type. */ using base_type = underlying_type; /*! @brief Allocator type. */ using allocator_type = Allocator; /*! @brief Type of the objects assigned to entities. */ using value_type = Type; /*! @brief Underlying entity identifier. */ using entity_type = Entity; /*! @brief Unsigned integer type. */ using size_type = std::size_t; /*! @brief Pointer type to contained elements. */ using pointer = typename container_type::pointer; /*! @brief Constant pointer type to contained elements. */ using const_pointer = typename alloc_traits::template rebind_traits::const_pointer; /*! @brief Random access iterator type. */ using iterator = internal::storage_iterator; /*! @brief Constant random access iterator type. */ using const_iterator = internal::storage_iterator; /*! @brief Reverse iterator type. */ using reverse_iterator = std::reverse_iterator; /*! @brief Constant reverse iterator type. */ using const_reverse_iterator = std::reverse_iterator; /*! @brief Extended iterable storage proxy. */ using iterable = iterable_adaptor>; /*! @brief Constant extended iterable storage proxy. */ using const_iterable = iterable_adaptor>; /*! @brief Default constructor. */ basic_storage() : basic_storage{allocator_type{}} {} /** * @brief Constructs an empty storage with a given allocator. * @param allocator The allocator to use. */ explicit basic_storage(const allocator_type &allocator) : base_type{type_id(), deletion_policy{comp_traits::in_place_delete}, allocator}, packed{container_type{allocator}, allocator} {} /** * @brief Move constructor. * @param other The instance to move from. */ basic_storage(basic_storage &&other) ENTT_NOEXCEPT : base_type{std::move(other)}, packed{std::move(other.packed)} {} /** * @brief Allocator-extended move constructor. * @param other The instance to move from. * @param allocator The allocator to use. */ basic_storage(basic_storage &&other, const allocator_type &allocator) ENTT_NOEXCEPT : base_type{std::move(other), allocator}, packed{container_type{std::move(other.packed.first()), allocator}, allocator} { ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed"); } /*! @brief Default destructor. */ ~basic_storage() override { shrink_to_size(0u); } /** * @brief Move assignment operator. * @param other The instance to move from. * @return This storage. */ basic_storage &operator=(basic_storage &&other) ENTT_NOEXCEPT { ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed"); shrink_to_size(0u); base_type::operator=(std::move(other)); packed.first() = std::move(other.packed.first()); propagate_on_container_move_assignment(packed.second(), other.packed.second()); return *this; } /** * @brief Exchanges the contents with those of a given storage. * @param other Storage to exchange the content with. */ void swap(basic_storage &other) { using std::swap; underlying_type::swap(other); propagate_on_container_swap(packed.second(), other.packed.second()); swap(packed.first(), other.packed.first()); } /** * @brief Returns the associated allocator. * @return The associated allocator. */ [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { return allocator_type{packed.second()}; } /** * @brief Increases the capacity of a storage. * * If the new capacity is greater than the current capacity, new storage is * allocated, otherwise the method does nothing. * * @param cap Desired capacity. */ void reserve(const size_type cap) override { if(cap != 0u) { base_type::reserve(cap); assure_at_least(cap - 1u); } } /** * @brief Returns the number of elements that a storage has currently * allocated space for. * @return Capacity of the storage. */ [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT override { return packed.first().size() * comp_traits::page_size; } /*! @brief Requests the removal of unused capacity. */ void shrink_to_fit() override { base_type::shrink_to_fit(); shrink_to_size(base_type::size()); } /** * @brief Direct access to the array of objects. * @return A pointer to the array of objects. */ [[nodiscard]] const_pointer raw() const ENTT_NOEXCEPT { return packed.first().data(); } /*! @copydoc raw */ [[nodiscard]] pointer raw() ENTT_NOEXCEPT { return packed.first().data(); } /** * @brief Returns an iterator to the beginning. * * The returned iterator points to the first instance of the internal array. * If the storage is empty, the returned iterator will be equal to `end()`. * * @return An iterator to the first instance of the internal array. */ [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { const auto pos = static_cast(base_type::size()); return const_iterator{&packed.first(), pos}; } /*! @copydoc cbegin */ [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { return cbegin(); } /*! @copydoc begin */ [[nodiscard]] iterator begin() ENTT_NOEXCEPT { const auto pos = static_cast(base_type::size()); return iterator{&packed.first(), pos}; } /** * @brief Returns an iterator to the end. * * The returned iterator points to the element following the last instance * of the internal array. Attempting to dereference the returned iterator * results in undefined behavior. * * @return An iterator to the element following the last instance of the * internal array. */ [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { return const_iterator{&packed.first(), {}}; } /*! @copydoc cend */ [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { return cend(); } /*! @copydoc end */ [[nodiscard]] iterator end() ENTT_NOEXCEPT { return iterator{&packed.first(), {}}; } /** * @brief Returns a reverse iterator to the beginning. * * The returned iterator points to the first instance of the reversed * internal array. If the storage is empty, the returned iterator will be * equal to `rend()`. * * @return An iterator to the first instance of the reversed internal array. */ [[nodiscard]] const_reverse_iterator crbegin() const ENTT_NOEXCEPT { return std::make_reverse_iterator(cend()); } /*! @copydoc crbegin */ [[nodiscard]] const_reverse_iterator rbegin() const ENTT_NOEXCEPT { return crbegin(); } /*! @copydoc rbegin */ [[nodiscard]] reverse_iterator rbegin() ENTT_NOEXCEPT { return std::make_reverse_iterator(end()); } /** * @brief Returns a reverse iterator to the end. * * The returned iterator points to the element following the last instance * of the reversed internal array. Attempting to dereference the returned * iterator results in undefined behavior. * * @return An iterator to the element following the last instance of the * reversed internal array. */ [[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT { return std::make_reverse_iterator(cbegin()); } /*! @copydoc crend */ [[nodiscard]] const_reverse_iterator rend() const ENTT_NOEXCEPT { return crend(); } /*! @copydoc rend */ [[nodiscard]] reverse_iterator rend() ENTT_NOEXCEPT { return std::make_reverse_iterator(begin()); } /** * @brief Returns the object assigned to an entity. * * @warning * Attempting to use an entity that doesn't belong to the storage results in * undefined behavior. * * @param entt A valid identifier. * @return The object assigned to the entity. */ [[nodiscard]] const value_type &get(const entity_type entt) const ENTT_NOEXCEPT { return element_at(base_type::index(entt)); } /*! @copydoc get */ [[nodiscard]] value_type &get(const entity_type entt) ENTT_NOEXCEPT { return const_cast(std::as_const(*this).get(entt)); } /** * @brief Returns the object assigned to an entity as a tuple. * @param entt A valid identifier. * @return The object assigned to the entity as a tuple. */ [[nodiscard]] std::tuple get_as_tuple(const entity_type entt) const ENTT_NOEXCEPT { return std::forward_as_tuple(get(entt)); } /*! @copydoc get_as_tuple */ [[nodiscard]] std::tuple get_as_tuple(const entity_type entt) ENTT_NOEXCEPT { return std::forward_as_tuple(get(entt)); } /** * @brief Assigns an entity to a storage and constructs its object. * * @warning * Attempting to use an entity that already belongs to the storage results * in undefined behavior. * * @tparam Args Types of arguments to use to construct the object. * @param entt A valid identifier. * @param args Parameters to use to construct an object for the entity. * @return A reference to the newly created object. */ template value_type &emplace(const entity_type entt, Args &&...args) { if constexpr(std::is_aggregate_v) { const auto it = emplace_element(entt, false, Type{std::forward(args)...}); return element_at(static_cast(it.index())); } else { const auto it = emplace_element(entt, false, std::forward(args)...); return element_at(static_cast(it.index())); } } /** * @brief Updates the instance assigned to a given entity in-place. * @tparam Func Types of the function objects to invoke. * @param entt A valid identifier. * @param func Valid function objects. * @return A reference to the updated instance. */ template value_type &patch(const entity_type entt, Func &&...func) { const auto idx = base_type::index(entt); auto &elem = element_at(idx); (std::forward(func)(elem), ...); return elem; } /** * @brief Assigns one or more entities to a storage and constructs their * objects from a given instance. * * @warning * Attempting to assign an entity that already belongs to the storage * results in undefined behavior. * * @tparam It Type of input iterator. * @param first An iterator to the first element of the range of entities. * @param last An iterator past the last element of the range of entities. * @param value An instance of the object to construct. */ template void insert(It first, It last, const value_type &value = {}) { for(; first != last; ++first) { emplace_element(*first, true, value); } } /** * @brief Assigns one or more entities to a storage and constructs their * objects from a given range. * * @sa construct * * @tparam EIt Type of input iterator. * @tparam CIt Type of input iterator. * @param first An iterator to the first element of the range of entities. * @param last An iterator past the last element of the range of entities. * @param from An iterator to the first element of the range of objects. */ template::value_type, value_type>>> void insert(EIt first, EIt last, CIt from) { for(; first != last; ++first, ++from) { emplace_element(*first, true, *from); } } /** * @brief Returns an iterable object to use to _visit_ a storage. * * The iterable object returns a tuple that contains the current entity and * a reference to its component. * * @return An iterable object to use to _visit_ the storage. */ [[nodiscard]] iterable each() ENTT_NOEXCEPT { return {internal::extended_storage_iterator{base_type::begin(), begin()}, internal::extended_storage_iterator{base_type::end(), end()}}; } /*! @copydoc each */ [[nodiscard]] const_iterable each() const ENTT_NOEXCEPT { return {internal::extended_storage_iterator{base_type::cbegin(), cbegin()}, internal::extended_storage_iterator{base_type::cend(), cend()}}; } private: compressed_pair packed; }; /*! @copydoc basic_storage */ template class basic_storage>> : public basic_sparse_set::template rebind_alloc> { using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); using underlying_type = basic_sparse_set>; using comp_traits = component_traits; public: /*! @brief Base type. */ using base_type = underlying_type; /*! @brief Allocator type. */ using allocator_type = Allocator; /*! @brief Type of the objects assigned to entities. */ using value_type = Type; /*! @brief Underlying entity identifier. */ using entity_type = Entity; /*! @brief Unsigned integer type. */ using size_type = std::size_t; /*! @brief Extended iterable storage proxy. */ using iterable = iterable_adaptor>; /*! @brief Constant extended iterable storage proxy. */ using const_iterable = iterable_adaptor>; /*! @brief Default constructor. */ basic_storage() : basic_storage{allocator_type{}} {} /** * @brief Constructs an empty container with a given allocator. * @param allocator The allocator to use. */ explicit basic_storage(const allocator_type &allocator) : base_type{type_id(), deletion_policy{comp_traits::in_place_delete}, allocator} {} /** * @brief Move constructor. * @param other The instance to move from. */ basic_storage(basic_storage &&other) ENTT_NOEXCEPT = default; /** * @brief Allocator-extended move constructor. * @param other The instance to move from. * @param allocator The allocator to use. */ basic_storage(basic_storage &&other, const allocator_type &allocator) ENTT_NOEXCEPT : base_type{std::move(other), allocator} {} /** * @brief Move assignment operator. * @param other The instance to move from. * @return This storage. */ basic_storage &operator=(basic_storage &&other) ENTT_NOEXCEPT = default; /** * @brief Returns the associated allocator. * @return The associated allocator. */ [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { return allocator_type{base_type::get_allocator()}; } /** * @brief Returns the object assigned to an entity, that is `void`. * * @warning * Attempting to use an entity that doesn't belong to the storage results in * undefined behavior. * * @param entt A valid identifier. */ void get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); } /** * @brief Returns an empty tuple. * * @warning * Attempting to use an entity that doesn't belong to the storage results in * undefined behavior. * * @param entt A valid identifier. * @return Returns an empty tuple. */ [[nodiscard]] std::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); return std::tuple{}; } /** * @brief Assigns an entity to a storage and constructs its object. * * @warning * Attempting to use an entity that already belongs to the storage results * in undefined behavior. * * @tparam Args Types of arguments to use to construct the object. * @param entt A valid identifier. */ template void emplace(const entity_type entt, Args &&...) { base_type::try_emplace(entt, false); } /** * @brief Updates the instance assigned to a given entity in-place. * @tparam Func Types of the function objects to invoke. * @param entt A valid identifier. * @param func Valid function objects. */ template void patch([[maybe_unused]] const entity_type entt, Func &&...func) { ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); (std::forward(func)(), ...); } /** * @brief Assigns entities to a storage. * @tparam It Type of input iterator. * @tparam Args Types of optional arguments. * @param first An iterator to the first element of the range of entities. * @param last An iterator past the last element of the range of entities. */ template void insert(It first, It last, Args &&...) { for(; first != last; ++first) { base_type::try_emplace(*first, true); } } /** * @brief Returns an iterable object to use to _visit_ a storage. * * The iterable object returns a tuple that contains the current entity. * * @return An iterable object to use to _visit_ the storage. */ [[nodiscard]] iterable each() ENTT_NOEXCEPT { return {internal::extended_storage_iterator{base_type::begin()}, internal::extended_storage_iterator{base_type::end()}}; } /*! @copydoc each */ [[nodiscard]] const_iterable each() const ENTT_NOEXCEPT { return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}}; } }; /** * @brief Provides a common way to access certain properties of storage types. * @tparam Entity A valid entity type (see entt_traits for more details). * @tparam Type Type of objects managed by the storage class. */ template struct storage_traits { /*! @brief Resulting type after component-to-storage conversion. */ using storage_type = sigh_storage_mixin>; }; } // namespace entt #endif