#ifndef ENTT_ENTITY_REGISTRY_HPP #define ENTT_ENTITY_REGISTRY_HPP #include #include #include #include #include #include #include #include #include #include "../config/config.h" #include "../container/dense_map.hpp" #include "../core/algorithm.hpp" #include "../core/any.hpp" #include "../core/fwd.hpp" #include "../core/iterator.hpp" #include "../core/memory.hpp" #include "../core/type_info.hpp" #include "../core/type_traits.hpp" #include "../core/utility.hpp" #include "entity.hpp" #include "fwd.hpp" #include "group.hpp" #include "mixin.hpp" #include "sparse_set.hpp" #include "storage.hpp" #include "view.hpp" namespace entt { /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ namespace internal { template class registry_storage_iterator final { template friend class registry_storage_iterator; using mapped_type = std::remove_reference_t()->second)>; public: using value_type = std::pair &>; using pointer = input_iterator_pointer; using reference = value_type; using difference_type = std::ptrdiff_t; using iterator_category = std::input_iterator_tag; using iterator_concept = std::random_access_iterator_tag; constexpr registry_storage_iterator() noexcept : it{} {} constexpr registry_storage_iterator(It iter) noexcept : it{iter} {} template && std::is_constructible_v>> constexpr registry_storage_iterator(const registry_storage_iterator &other) noexcept : registry_storage_iterator{other.it} {} constexpr registry_storage_iterator &operator++() noexcept { return ++it, *this; } constexpr registry_storage_iterator operator++(int) noexcept { registry_storage_iterator orig = *this; return ++(*this), orig; } constexpr registry_storage_iterator &operator--() noexcept { return --it, *this; } constexpr registry_storage_iterator operator--(int) noexcept { registry_storage_iterator orig = *this; return operator--(), orig; } constexpr registry_storage_iterator &operator+=(const difference_type value) noexcept { it += value; return *this; } constexpr registry_storage_iterator operator+(const difference_type value) const noexcept { registry_storage_iterator copy = *this; return (copy += value); } constexpr registry_storage_iterator &operator-=(const difference_type value) noexcept { return (*this += -value); } constexpr registry_storage_iterator operator-(const difference_type value) const noexcept { return (*this + -value); } [[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { return {it[value].first, *it[value].second}; } [[nodiscard]] constexpr reference operator*() const noexcept { return {it->first, *it->second}; } [[nodiscard]] constexpr pointer operator->() const noexcept { return operator*(); } template friend constexpr std::ptrdiff_t operator-(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; template friend constexpr bool operator==(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; template friend constexpr bool operator<(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; private: It it; }; template [[nodiscard]] constexpr std::ptrdiff_t operator-(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return lhs.it - rhs.it; } template [[nodiscard]] constexpr bool operator==(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return lhs.it == rhs.it; } template [[nodiscard]] constexpr bool operator!=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return !(lhs == rhs); } template [[nodiscard]] constexpr bool operator<(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return lhs.it < rhs.it; } template [[nodiscard]] constexpr bool operator>(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return rhs < lhs; } template [[nodiscard]] constexpr bool operator<=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return !(lhs > rhs); } template [[nodiscard]] constexpr bool operator>=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return !(lhs < rhs); } template class registry_context { using alloc_traits = std::allocator_traits; using allocator_type = typename alloc_traits::template rebind_alloc>>; public: explicit registry_context(const allocator_type &allocator) : ctx{allocator} {} template Type &emplace_as(const id_type id, Args &&...args) { return any_cast(ctx.try_emplace(id, std::in_place_type, std::forward(args)...).first->second); } template Type &emplace(Args &&...args) { return emplace_as(type_id().hash(), std::forward(args)...); } template Type &insert_or_assign(const id_type id, Type &&value) { return any_cast> &>(ctx.insert_or_assign(id, std::forward(value)).first->second); } template Type &insert_or_assign(Type &&value) { return insert_or_assign(type_id().hash(), std::forward(value)); } template bool erase(const id_type id = type_id().hash()) { const auto it = ctx.find(id); return it != ctx.end() && it->second.type() == type_id() ? (ctx.erase(it), true) : false; } template [[nodiscard]] const Type &get(const id_type id = type_id().hash()) const { return any_cast(ctx.at(id)); } template [[nodiscard]] Type &get(const id_type id = type_id().hash()) { return any_cast(ctx.at(id)); } template [[nodiscard]] const Type *find(const id_type id = type_id().hash()) const { const auto it = ctx.find(id); return it != ctx.cend() ? any_cast(&it->second) : nullptr; } template [[nodiscard]] Type *find(const id_type id = type_id().hash()) { const auto it = ctx.find(id); return it != ctx.end() ? any_cast(&it->second) : nullptr; } template [[nodiscard]] bool contains(const id_type id = type_id().hash()) const { const auto it = ctx.find(id); return it != ctx.cend() && it->second.type() == type_id(); } private: dense_map, identity, std::equal_to, allocator_type> ctx; }; } // namespace internal /** * Internal details not to be documented. * @endcond */ /** * @brief Fast and reliable entity-component system. * @tparam Entity A valid entity type. * @tparam Allocator Type of allocator used to manage memory and elements. */ template class basic_registry { using base_type = basic_sparse_set; using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); // std::shared_ptr because of its type erased allocator which is useful here using pool_container_type = dense_map, identity, std::equal_to, typename alloc_traits::template rebind_alloc>>>; using group_container_type = dense_map, identity, std::equal_to, typename alloc_traits::template rebind_alloc>>>; template [[nodiscard]] auto &assure([[maybe_unused]] const id_type id = type_hash::value()) { if constexpr(std::is_same_v) { return entities; } else { static_assert(std::is_same_v>, "Non-decayed types not allowed"); auto &cpool = pools[id]; if(!cpool) { using storage_type = storage_for_type; using alloc_type = typename storage_type::allocator_type; if constexpr(std::is_void_v && !std::is_constructible_v) { // std::allocator has no cross constructors (waiting for C++20) cpool = std::allocate_shared(get_allocator(), alloc_type{}); } else { cpool = std::allocate_shared(get_allocator(), get_allocator()); } cpool->bind(forward_as_any(*this)); } ENTT_ASSERT(cpool->type() == type_id(), "Unexpected type"); return static_cast &>(*cpool); } } template [[nodiscard]] const auto *assure([[maybe_unused]] const id_type id = type_hash::value()) const { if constexpr(std::is_same_v) { return &entities; } else { static_assert(std::is_same_v>, "Non-decayed types not allowed"); if(const auto it = pools.find(id); it != pools.cend()) { ENTT_ASSERT(it->second->type() == type_id(), "Unexpected type"); return static_cast *>(it->second.get()); } return static_cast *>(nullptr); } } void rebind() { entities.bind(forward_as_any(*this)); for(auto &&curr: pools) { curr.second->bind(forward_as_any(*this)); } } public: /*! @brief Entity traits. */ using traits_type = typename base_type::traits_type; /*! @brief Allocator type. */ using allocator_type = Allocator; /*! @brief Underlying entity identifier. */ using entity_type = typename traits_type::value_type; /*! @brief Underlying version type. */ using version_type = typename traits_type::version_type; /*! @brief Unsigned integer type. */ using size_type = std::size_t; /*! @brief Common type among all storage types. */ using common_type = base_type; /*! @brief Context type. */ using context = internal::registry_context; /** * @copybrief storage_for * @tparam Type Storage value type, eventually const. */ template using storage_for_type = typename storage_for>>::type; /*! @brief Default constructor. */ basic_registry() : basic_registry{allocator_type{}} {} /** * @brief Constructs an empty registry with a given allocator. * @param allocator The allocator to use. */ explicit basic_registry(const allocator_type &allocator) : basic_registry{0u, allocator} {} /** * @brief Allocates enough memory upon construction to store `count` pools. * @param count The number of pools to allocate memory for. * @param allocator The allocator to use. */ basic_registry(const size_type count, const allocator_type &allocator = allocator_type{}) : vars{allocator}, pools{allocator}, groups{allocator}, entities{allocator} { pools.reserve(count); rebind(); } /** * @brief Move constructor. * @param other The instance to move from. */ basic_registry(basic_registry &&other) noexcept : vars{std::move(other.vars)}, pools{std::move(other.pools)}, groups{std::move(other.groups)}, entities{std::move(other.entities)} { rebind(); } /** * @brief Move assignment operator. * @param other The instance to move from. * @return This registry. */ basic_registry &operator=(basic_registry &&other) noexcept { vars = std::move(other.vars); pools = std::move(other.pools); groups = std::move(other.groups); entities = std::move(other.entities); rebind(); return *this; } /** * @brief Exchanges the contents with those of a given registry. * @param other Registry to exchange the content with. */ void swap(basic_registry &other) { using std::swap; swap(vars, other.vars); swap(pools, other.pools); swap(groups, other.groups); swap(entities, other.entities); rebind(); other.rebind(); } /** * @brief Returns the associated allocator. * @return The associated allocator. */ [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { return entities.get_allocator(); } /** * @brief Returns an iterable object to use to _visit_ a registry. * * The iterable object returns a pair that contains the name and a reference * to the current storage. * * @return An iterable object to use to _visit_ the registry. */ [[nodiscard]] auto storage() noexcept { return iterable_adaptor{internal::registry_storage_iterator{pools.begin()}, internal::registry_storage_iterator{pools.end()}}; } /*! @copydoc storage */ [[nodiscard]] auto storage() const noexcept { return iterable_adaptor{internal::registry_storage_iterator{pools.cbegin()}, internal::registry_storage_iterator{pools.cend()}}; } /** * @brief Finds the storage associated with a given name, if any. * @param id Name used to map the storage within the registry. * @return A pointer to the storage if it exists, a null pointer otherwise. */ [[nodiscard]] common_type *storage(const id_type id) { return const_cast(std::as_const(*this).storage(id)); } /** * @brief Finds the storage associated with a given name, if any. * @param id Name used to map the storage within the registry. * @return A pointer to the storage if it exists, a null pointer otherwise. */ [[nodiscard]] const common_type *storage(const id_type id) const { const auto it = pools.find(id); return it == pools.cend() ? nullptr : it->second.get(); } /** * @brief Returns the storage for a given component type. * @tparam Type Type of component of which to return the storage. * @param id Optional name used to map the storage within the registry. * @return The storage for the given component type. */ template storage_for_type &storage(const id_type id = type_hash::value()) { return assure(id); } /** * @brief Returns the storage for a given component type, if any. * @tparam Type Type of component of which to return the storage. * @param id Optional name used to map the storage within the registry. * @return The storage for the given component type. */ template const storage_for_type *storage(const id_type id = type_hash::value()) const { return assure(id); } /** * @brief Checks if an identifier refers to a valid entity. * @param entt An identifier, either valid or not. * @return True if the identifier is valid, false otherwise. */ [[nodiscard]] bool valid(const entity_type entt) const { return entities.contains(entt) && (entities.index(entt) < entities.free_list()); } /** * @brief Returns the actual version for an identifier. * @param entt A valid identifier. * @return The version for the given identifier if valid, the tombstone * version otherwise. */ [[nodiscard]] version_type current(const entity_type entt) const { return entities.current(entt); } /** * @brief Creates a new entity or recycles a destroyed one. * @return A valid identifier. */ [[nodiscard]] entity_type create() { return entities.emplace(); } /** * @copybrief create * * If the requested entity isn't in use, the suggested identifier is used. * Otherwise, a new identifier is generated. * * @param hint Required identifier. * @return A valid identifier. */ [[nodiscard]] entity_type create(const entity_type hint) { return entities.emplace(hint); } /** * @brief Assigns each element in a range an identifier. * * @sa create * * @tparam It Type of forward iterator. * @param first An iterator to the first element of the range to generate. * @param last An iterator past the last element of the range to generate. */ template void create(It first, It last) { entities.insert(std::move(first), std::move(last)); } /** * @brief Destroys an entity and releases its identifier. * * @warning * Adding or removing components to an entity that is being destroyed can * result in undefined behavior. * * @param entt A valid identifier. * @return The version of the recycled entity. */ version_type destroy(const entity_type entt) { for(size_type pos = pools.size(); pos; --pos) { pools.begin()[pos - 1u].second->remove(entt); } entities.erase(entt); return entities.current(entt); } /** * @brief Destroys an entity and releases its identifier. * * The suggested version or the valid version closest to the suggested one * is used instead of the implicitly generated version. * * @sa destroy * * @param entt A valid identifier. * @param version A desired version upon destruction. * @return The version actually assigned to the entity. */ version_type destroy(const entity_type entt, const version_type version) { destroy(entt); const auto elem = traits_type::construct(traits_type::to_entity(entt), version); return entities.bump((elem == tombstone) ? traits_type::next(elem) : elem); } /** * @brief Destroys all entities in a range and releases their identifiers. * * @sa destroy * * @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. */ template void destroy(It first, It last) { const auto from = entities.each().cbegin().base(); const auto to = from + entities.pack(first, last); for(size_type pos = pools.size(); pos; --pos) { pools.begin()[pos - 1u].second->remove(from, to); } entities.erase(from, to); } /** * @brief Assigns the given component to an entity. * * The component must have a proper constructor or be of aggregate type. * * @warning * Attempting to assign a component to an entity that already owns it * results in undefined behavior. * * @tparam Type Type of component to create. * @tparam Args Types of arguments to use to construct the component. * @param entt A valid identifier. * @param args Parameters to use to initialize the component. * @return A reference to the newly created component. */ template decltype(auto) emplace(const entity_type entt, Args &&...args) { return assure().emplace(entt, std::forward(args)...); } /** * @brief Assigns each entity in a range the given component. * * @sa emplace * * @tparam Type Type of component to create. * @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 component to assign. */ template void insert(It first, It last, const Type &value = {}) { assure().insert(std::move(first), std::move(last), value); } /** * @brief Assigns each entity in a range the given components. * * @sa emplace * * @tparam Type Type of component to create. * @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 components. */ template::value_type, Type>>> void insert(EIt first, EIt last, CIt from) { assure().insert(first, last, from); } /** * @brief Assigns or replaces the given component for an entity. * * @sa emplace * @sa replace * * @tparam Type Type of component to assign or replace. * @tparam Args Types of arguments to use to construct the component. * @param entt A valid identifier. * @param args Parameters to use to initialize the component. * @return A reference to the newly created component. */ template decltype(auto) emplace_or_replace(const entity_type entt, Args &&...args) { if(auto &cpool = assure(); cpool.contains(entt)) { return cpool.patch(entt, [&args...](auto &...curr) { ((curr = Type{std::forward(args)...}), ...); }); } else { return cpool.emplace(entt, std::forward(args)...); } } /** * @brief Patches the given component for an entity. * * The signature of the function should be equivalent to the following: * * @code{.cpp} * void(Type &); * @endcode * * @warning * Attempting to patch a component of an entity that doesn't own it * results in undefined behavior. * * @tparam Type Type of component to patch. * @tparam Func Types of the function objects to invoke. * @param entt A valid identifier. * @param func Valid function objects. * @return A reference to the patched component. */ template decltype(auto) patch(const entity_type entt, Func &&...func) { return assure().patch(entt, std::forward(func)...); } /** * @brief Replaces the given component for an entity. * * The component must have a proper constructor or be of aggregate type. * * @warning * Attempting to replace a component of an entity that doesn't own it * results in undefined behavior. * * @tparam Type Type of component to replace. * @tparam Args Types of arguments to use to construct the component. * @param entt A valid identifier. * @param args Parameters to use to initialize the component. * @return A reference to the component being replaced. */ template decltype(auto) replace(const entity_type entt, Args &&...args) { return patch(entt, [&args...](auto &...curr) { ((curr = Type{std::forward(args)...}), ...); }); } /** * @brief Removes the given components from an entity. * @tparam Type Type of component to remove. * @tparam Other Other types of components to remove. * @param entt A valid identifier. * @return The number of components actually removed. */ template size_type remove(const entity_type entt) { return (assure().remove(entt) + ... + assure().remove(entt)); } /** * @brief Removes the given components from all the entities in a range. * * @sa remove * * @tparam Type Type of component to remove. * @tparam Other Other types of components to remove. * @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. * @return The number of components actually removed. */ template size_type remove(It first, It last) { size_type count{}; if constexpr(std::is_same_v) { common_type *cpools[sizeof...(Other) + 1u]{&assure(), &assure()...}; for(size_type pos{}, len = sizeof...(Other) + 1u; pos < len; ++pos) { if constexpr(sizeof...(Other) != 0u) { if(cpools[pos]->data() == first.data()) { std::swap(cpools[pos], cpools[sizeof...(Other)]); } } count += cpools[pos]->remove(first, last); } } else { for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { count += std::apply([entt = *first](auto &...curr) { return (curr.remove(entt) + ... + 0u); }, cpools); } } return count; } /** * @brief Erases the given components from an entity. * * @warning * Attempting to erase a component from an entity that doesn't own it * results in undefined behavior. * * @tparam Type Types of components to erase. * @tparam Other Other types of components to erase. * @param entt A valid identifier. */ template void erase(const entity_type entt) { (assure().erase(entt), (assure().erase(entt), ...)); } /** * @brief Erases the given components from all the entities in a range. * * @sa erase * * @tparam Type Types of components to erase. * @tparam Other Other types of components to erase. * @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. */ template void erase(It first, It last) { if constexpr(std::is_same_v) { common_type *cpools[sizeof...(Other) + 1u]{&assure(), &assure()...}; for(size_type pos{}, len = sizeof...(Other) + 1u; pos < len; ++pos) { if constexpr(sizeof...(Other) != 0u) { if(cpools[pos]->data() == first.data()) { std::swap(cpools[pos], cpools[sizeof...(Other)]); } } cpools[pos]->erase(first, last); } } else { for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { std::apply([entt = *first](auto &...curr) { (curr.erase(entt), ...); }, cpools); } } } /** * @brief Erases components satisfying specific criteria from an entity. * * The function type is equivalent to: * * @code{.cpp} * void(const id_type, typename basic_registry::base_type &); * @endcode * * Only storage where the entity exists are passed to the function. * * @tparam Func Type of the function object to invoke. * @param entt A valid identifier. * @param func A valid function object. */ template void erase_if(const entity_type entt, Func func) { for(auto [id, cpool]: storage()) { if(cpool.contains(entt) && func(id, std::as_const(cpool))) { cpool.erase(entt); } } } /** * @brief Removes all tombstones from a registry or only the pools for the * given components. * @tparam Type Types of components for which to clear all tombstones. */ template void compact() { if constexpr(sizeof...(Type) == 0u) { for(auto &&curr: pools) { curr.second->compact(); } } else { (assure().compact(), ...); } } /** * @brief Check if an entity is part of all the given storage. * @tparam Type Type of storage to check for. * @param entt A valid identifier. * @return True if the entity is part of all the storage, false otherwise. */ template [[nodiscard]] bool all_of([[maybe_unused]] const entity_type entt) const { if constexpr(sizeof...(Type) == 1u) { auto *cpool = assure...>(); return cpool && cpool->contains(entt); } else { return (all_of(entt) && ...); } } /** * @brief Check if an entity is part of at least one given storage. * @tparam Type Type of storage to check for. * @param entt A valid identifier. * @return True if the entity is part of at least one storage, false * otherwise. */ template [[nodiscard]] bool any_of([[maybe_unused]] const entity_type entt) const { return (all_of(entt) || ...); } /** * @brief Returns references to the given components for an entity. * * @warning * Attempting to get a component from an entity that doesn't own it results * in undefined behavior. * * @tparam Type Types of components to get. * @param entt A valid identifier. * @return References to the components owned by the entity. */ template [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const { if constexpr(sizeof...(Type) == 1u) { return (assure>()->get(entt), ...); } else { return std::forward_as_tuple(get(entt)...); } } /*! @copydoc get */ template [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) { if constexpr(sizeof...(Type) == 1u) { return (static_cast &>(assure>()).get(entt), ...); } else { return std::forward_as_tuple(get(entt)...); } } /** * @brief Returns a reference to the given component for an entity. * * In case the entity doesn't own the component, the parameters provided are * used to construct it. * * @sa get * @sa emplace * * @tparam Type Type of component to get. * @tparam Args Types of arguments to use to construct the component. * @param entt A valid identifier. * @param args Parameters to use to initialize the component. * @return Reference to the component owned by the entity. */ template [[nodiscard]] decltype(auto) get_or_emplace(const entity_type entt, Args &&...args) { if(auto &cpool = assure(); cpool.contains(entt)) { return cpool.get(entt); } else { return cpool.emplace(entt, std::forward(args)...); } } /** * @brief Returns pointers to the given components for an entity. * * @note * The registry retains ownership of the pointed-to components. * * @tparam Type Types of components to get. * @param entt A valid identifier. * @return Pointers to the components owned by the entity. */ template [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entt) const { if constexpr(sizeof...(Type) == 1u) { const auto *cpool = assure...>(); return (cpool && cpool->contains(entt)) ? std::addressof(cpool->get(entt)) : nullptr; } else { return std::make_tuple(try_get(entt)...); } } /*! @copydoc try_get */ template [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entt) { if constexpr(sizeof...(Type) == 1u) { return (const_cast(std::as_const(*this).template try_get(entt)), ...); } else { return std::make_tuple(try_get(entt)...); } } /** * @brief Clears a whole registry or the pools for the given components. * @tparam Type Types of components to remove from their entities. */ template void clear() { if constexpr(sizeof...(Type) == 0u) { for(size_type pos = pools.size(); pos; --pos) { pools.begin()[pos - 1u].second->clear(); } const auto iterable = entities.each(); entities.erase(iterable.begin().base(), iterable.end().base()); } else { (assure().clear(), ...); } } /** * @brief Checks if an entity has components assigned. * @param entt A valid identifier. * @return True if the entity has no components assigned, false otherwise. */ [[nodiscard]] bool orphan(const entity_type entt) const { return std::none_of(pools.cbegin(), pools.cend(), [entt](auto &&curr) { return curr.second->contains(entt); }); } /** * @brief Returns a sink object for the given component. * * Use this function to receive notifications whenever a new instance of the * given component is created and assigned to an entity.
* The function type for a listener is equivalent to: * * @code{.cpp} * void(basic_registry &, Entity); * @endcode * * Listeners are invoked **after** assigning the component to the entity. * * @sa sink * * @tparam Type Type of component of which to get the sink. * @param id Optional name used to map the storage within the registry. * @return A temporary sink object. */ template [[nodiscard]] auto on_construct(const id_type id = type_hash::value()) { return assure(id).on_construct(); } /** * @brief Returns a sink object for the given component. * * Use this function to receive notifications whenever an instance of the * given component is explicitly updated.
* The function type for a listener is equivalent to: * * @code{.cpp} * void(basic_registry &, Entity); * @endcode * * Listeners are invoked **after** updating the component. * * @sa sink * * @tparam Type Type of component of which to get the sink. * @param id Optional name used to map the storage within the registry. * @return A temporary sink object. */ template [[nodiscard]] auto on_update(const id_type id = type_hash::value()) { return assure(id).on_update(); } /** * @brief Returns a sink object for the given component. * * Use this function to receive notifications whenever an instance of the * given component is removed from an entity and thus destroyed.
* The function type for a listener is equivalent to: * * @code{.cpp} * void(basic_registry &, Entity); * @endcode * * Listeners are invoked **before** removing the component from the entity. * * @sa sink * * @tparam Type Type of component of which to get the sink. * @param id Optional name used to map the storage within the registry. * @return A temporary sink object. */ template [[nodiscard]] auto on_destroy(const id_type id = type_hash::value()) { return assure(id).on_destroy(); } /** * @brief Returns a view for the given components. * @tparam Type Type of component used to construct the view. * @tparam Other Other types of components used to construct the view. * @tparam Exclude Types of components used to filter the view. * @return A newly created view. */ template [[nodiscard]] basic_view, storage_for_type...>, exclude_t...>> view(exclude_t = exclude_t{}) const { const auto cpools = std::make_tuple(assure>(), assure>()..., assure>()...); basic_view, storage_for_type...>, exclude_t...>> elem{}; std::apply([&elem](const auto *...curr) { ((curr ? elem.storage(*curr) : void()), ...); }, cpools); return elem; } /*! @copydoc view */ template [[nodiscard]] basic_view, storage_for_type...>, exclude_t...>> view(exclude_t = exclude_t{}) { return {assure>(), assure>()..., assure>()...}; } /** * @brief Returns a group for the given components. * @tparam Owned Types of storage _owned_ by the group. * @tparam Get Types of storage _observed_ by the group, if any. * @tparam Exclude Types of storage used to filter the group, if any. * @return A newly created group. */ template basic_group...>, get_t...>, exclude_t...>> group(get_t = get_t{}, exclude_t = exclude_t{}) { using handler_type = typename basic_group...>, get_t...>, exclude_t...>>::handler; if(auto it = groups.find(type_hash::value()); it != groups.cend()) { return {*std::static_pointer_cast(it->second)}; } std::shared_ptr handler{}; if constexpr(sizeof...(Owned) == 0u) { handler = std::allocate_shared(get_allocator(), get_allocator(), assure>()..., assure>()...); } else { handler = std::allocate_shared(get_allocator(), assure>()..., assure>()..., assure>()...); [[maybe_unused]] const id_type elem[]{type_hash>::value()..., type_hash>::value()..., type_hash>::value()...}; ENTT_ASSERT(std::all_of(groups.cbegin(), groups.cend(), [&elem](const auto &data) { return data.second->owned(elem, sizeof...(Owned)) == 0u; }), "Conflicting groups"); } groups.emplace(type_hash::value(), handler); return {*handler}; } /*! @copydoc group */ template basic_group...>, get_t...>, exclude_t...>> group_if_exists(get_t = get_t{}, exclude_t = exclude_t{}) const { using handler_type = typename basic_group...>, get_t...>, exclude_t...>>::handler; if(auto it = groups.find(type_hash::value()); it != groups.cend()) { return {*std::static_pointer_cast(it->second)}; } return {}; } /** * @brief Checks whether the given components belong to any group. * @tparam Type Type of component in which one is interested. * @tparam Other Other types of components in which one is interested. * @return True if the pools of the given components are _free_, false * otherwise. */ template [[nodiscard]] bool owned() const { const id_type elem[]{type_hash>::value(), type_hash>::value()...}; return std::any_of(groups.cbegin(), groups.cend(), [&elem](auto &&data) { return data.second->owned(elem, 1u + sizeof...(Other)); }); } /** * @brief Sorts the elements of a given component. * * The comparison function object returns `true` if the first element is * _less_ than the second one, `false` otherwise. Its signature is also * equivalent to one of the following: * * @code{.cpp} * bool(const Entity, const Entity); * bool(const Type &, const Type &); * @endcode * * Moreover, it shall induce a _strict weak ordering_ on the values.
* The sort function object offers an `operator()` that accepts: * * * An iterator to the first element of the range to sort. * * An iterator past the last element of the range to sort. * * A comparison function object to use to compare the elements. * * The comparison function object hasn't necessarily the type of the one * passed along with the other parameters to this member function. * * @warning * Pools of components owned by a group cannot be sorted. * * @tparam Type Type of components to sort. * @tparam Compare Type of comparison function object. * @tparam Sort Type of sort function object. * @tparam Args Types of arguments to forward to the sort function object. * @param compare A valid comparison function object. * @param algo A valid sort function object. * @param args Arguments to forward to the sort function object, if any. */ template void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { ENTT_ASSERT(!owned(), "Cannot sort owned storage"); auto &cpool = assure(); if constexpr(std::is_invocable_v) { auto comp = [&cpool, compare = std::move(compare)](const auto lhs, const auto rhs) { return compare(std::as_const(cpool.get(lhs)), std::as_const(cpool.get(rhs))); }; cpool.sort(std::move(comp), std::move(algo), std::forward(args)...); } else { cpool.sort(std::move(compare), std::move(algo), std::forward(args)...); } } /** * @brief Sorts two pools of components in the same way. * * Entities and components in `To` which are part of both storage are sorted * internally with the order they have in `From`. The others follow in no * particular order. * * @warning * Pools of components owned by a group cannot be sorted. * * @tparam To Type of components to sort. * @tparam From Type of components to use to sort. */ template void sort() { ENTT_ASSERT(!owned(), "Cannot sort owned storage"); const base_type &cpool = assure(); assure().sort_as(cpool.begin(), cpool.end()); } /** * @brief Returns the context object, that is, a general purpose container. * @return The context object, that is, a general purpose container. */ context &ctx() noexcept { return vars; } /*! @copydoc ctx */ const context &ctx() const noexcept { return vars; } private: context vars; pool_container_type pools; group_container_type groups; storage_for_type entities; }; } // namespace entt #endif