#ifndef ENTT_ENTITY_MIXIN_HPP #define ENTT_ENTITY_MIXIN_HPP #include "../config/config.h" #include "../core/any.hpp" #include "../core/type_info.hpp" #include "../signal/sigh.hpp" #include "../stl/concepts.hpp" #include "../stl/iterator.hpp" #include "../stl/type_traits.hpp" #include "../stl/utility.hpp" #include "../stl/vector.hpp" #include "entity.hpp" #include "fwd.hpp" namespace entt { /*! @cond ENTT_INTERNAL */ namespace internal { template struct has_on_construct final: stl::false_type {}; template requires stl::invocable struct has_on_construct: stl::true_type {}; template struct has_on_update final: stl::false_type {}; template requires stl::invocable struct has_on_update: stl::true_type {}; template struct has_on_destroy final: stl::false_type {}; template requires stl::invocable struct has_on_destroy: stl::true_type {}; } // namespace internal /*! @endcond */ /** * @brief Mixin type used to add signal support to storage types. * * The function type of a listener is equivalent to: * * @code{.cpp} * void(basic_registry &, entity_type); * @endcode * * This applies to all signals made available. * * @tparam Type Underlying storage type. * @tparam Registry Basic registry type. */ template class basic_sigh_mixin final: public Type { using underlying_type = Type; using owner_type = Registry; using basic_registry_type = basic_registry; using sigh_type = sigh; using underlying_iterator = underlying_type::base_type::basic_iterator; static_assert(stl::is_base_of_v, "Invalid registry type"); [[nodiscard]] auto &owner_or_assert() const noexcept { ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); return static_cast(*owner); } private: void pop(underlying_iterator first, underlying_iterator last) final { if(auto ® = owner_or_assert(); destruction.empty()) { underlying_type::pop(first, last); } else { for(; first != last; ++first) { const auto entt = *first; destruction.publish(reg, entt); const auto it = underlying_type::find(entt); underlying_type::pop(it, it + 1u); } } } void pop_all() final { if(auto ® = owner_or_assert(); !destruction.empty()) { if constexpr(stl::is_same_v) { for(typename underlying_type::size_type pos{}, last = underlying_type::free_list(); pos < last; ++pos) { destruction.publish(reg, underlying_type::base_type::operator[](pos)); } } else { for(auto entt: static_cast(*this)) { if constexpr(underlying_type::storage_policy == deletion_policy::in_place) { if(entt != tombstone) { destruction.publish(reg, entt); } } else { destruction.publish(reg, entt); } } } } underlying_type::pop_all(); } underlying_iterator try_emplace(const underlying_type::entity_type entt, const bool force_back, const void *value) final { const auto it = underlying_type::try_emplace(entt, force_back, value); if(auto ® = owner_or_assert(); it != underlying_type::base_type::end()) { construction.publish(reg, *it); } return it; } void bind_any(any value) noexcept final { owner = any_cast(&value); if constexpr(!stl::is_same_v) { if(owner == nullptr) { owner = any_cast(&value); } } underlying_type::bind_any(stl::move(value)); } public: /*! @brief Allocator type. */ using allocator_type = underlying_type::allocator_type; /*! @brief Underlying entity identifier. */ using entity_type = underlying_type::entity_type; /*! @brief Expected registry type. */ using registry_type = owner_type; /*! @brief Default constructor. */ basic_sigh_mixin() : basic_sigh_mixin{allocator_type{}} {} /** * @brief Constructs an empty storage with a given allocator. * @param allocator The allocator to use. */ explicit basic_sigh_mixin(const allocator_type &allocator) : underlying_type{allocator}, owner{}, construction{allocator}, destruction{allocator}, update{allocator} { if constexpr(internal::has_on_construct::value) { sink{construction}.template connect<&underlying_type::element_type::on_construct>(); } if constexpr(internal::has_on_update::value) { sink{update}.template connect<&underlying_type::element_type::on_update>(); } if constexpr(internal::has_on_destroy::value) { sink{destruction}.template connect<&underlying_type::element_type::on_destroy>(); } } /*! @brief Default copy constructor, deleted on purpose. */ basic_sigh_mixin(const basic_sigh_mixin &) = delete; /** * @brief Move constructor. * @param other The instance to move from. */ basic_sigh_mixin(basic_sigh_mixin &&other) noexcept : underlying_type{static_cast(other)}, owner{other.owner}, construction{stl::move(other.construction)}, destruction{stl::move(other.destruction)}, update{stl::move(other.update)} {} /** * @brief Allocator-extended move constructor. * @param other The instance to move from. * @param allocator The allocator to use. */ basic_sigh_mixin(basic_sigh_mixin &&other, const allocator_type &allocator) : underlying_type{static_cast(other), allocator}, owner{other.owner}, construction{stl::move(other.construction), allocator}, destruction{stl::move(other.destruction), allocator}, update{stl::move(other.update), allocator} {} /*! @brief Default destructor. */ ~basic_sigh_mixin() override = default; /** * @brief Default copy assignment operator, deleted on purpose. * @return This mixin. */ basic_sigh_mixin &operator=(const basic_sigh_mixin &) = delete; /** * @brief Move assignment operator. * @param other The instance to move from. * @return This mixin. */ basic_sigh_mixin &operator=(basic_sigh_mixin &&other) noexcept { swap(other); return *this; } /** * @brief Exchanges the contents with those of a given storage. * @param other Storage to exchange the content with. */ void swap(basic_sigh_mixin &other) noexcept { using stl::swap; swap(owner, other.owner); swap(construction, other.construction); swap(destruction, other.destruction); swap(update, other.update); underlying_type::swap(other); } /** * @brief Returns a sink object. * * The sink returned by this function can be used to receive notifications * whenever a new instance is created and assigned to an entity.
* Listeners are invoked after the object has been assigned to the entity. * * @sa sink * * @return A temporary sink object. */ [[nodiscard]] auto on_construct() noexcept { return sink{construction}; } /** * @brief Returns a sink object. * * The sink returned by this function can be used to receive notifications * whenever an instance is explicitly updated.
* Listeners are invoked after the object has been updated. * * @sa sink * * @return A temporary sink object. */ [[nodiscard]] auto on_update() noexcept { return sink{update}; } /** * @brief Returns a sink object. * * The sink returned by this function can be used to receive notifications * whenever an instance is removed from an entity and thus destroyed.
* Listeners are invoked before the object has been removed from the entity. * * @sa sink * * @return A temporary sink object. */ [[nodiscard]] auto on_destroy() noexcept { return sink{destruction}; } /** * @brief Checks if a mixin refers to a valid registry. * @return True if the mixin refers to a valid registry, false otherwise. */ [[nodiscard]] explicit operator bool() const noexcept { return (owner != nullptr); } /** * @brief Returns a pointer to the underlying registry, if any. * @return A pointer to the underlying registry, if any. */ [[nodiscard]] const registry_type ®istry() const noexcept { return owner_or_assert(); } /*! @copydoc registry */ [[nodiscard]] registry_type ®istry() noexcept { return owner_or_assert(); } /** * @brief Creates a new identifier or recycles a destroyed one. * @return A valid identifier. */ auto generate() { const auto entt = underlying_type::generate(); construction.publish(owner_or_assert(), entt); return entt; } /** * @brief Creates a new identifier or recycles a destroyed one. * @param hint Required identifier. * @return A valid identifier. */ entity_type generate(const entity_type hint) { const auto entt = underlying_type::generate(hint); construction.publish(owner_or_assert(), entt); return entt; } /** * @brief Assigns each element in a range an identifier. * @tparam It Type of output 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 It> void generate(It first, It last) { underlying_type::generate(first, last); if(auto ® = owner_or_assert(); !construction.empty()) { for(; first != last; ++first) { construction.publish(reg, *first); } } } /** * @brief Assigns an entity to a storage and constructs its object. * @tparam Args Types of arguments to forward to the underlying storage. * @param entt A valid identifier. * @param args Parameters to forward to the underlying storage. * @return A reference to the newly created object. */ template decltype(auto) emplace(const entity_type entt, Args &&...args) { underlying_type::emplace(entt, stl::forward(args)...); construction.publish(owner_or_assert(), entt); return this->get(entt); } /** * @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 patched instance. */ template decltype(auto) patch(const entity_type entt, Func &&...func) { underlying_type::patch(entt, stl::forward(func)...); update.publish(owner_or_assert(), entt); return this->get(entt); } /** * @brief Assigns one or more entities to a storage and constructs their * objects from a given instance. * @tparam Args Types of arguments to forward to the underlying storage. * @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 args Parameters to use to forward to the underlying storage. */ template void insert(stl::input_iterator auto first, stl::input_iterator auto last, Args &&...args) { auto from = underlying_type::size(); underlying_type::insert(first, last, stl::forward(args)...); if(auto ® = owner_or_assert(); !construction.empty()) { // fine as long as insert passes force_back true to try_emplace for(const auto to = underlying_type::size(); from != to; ++from) { construction.publish(reg, underlying_type::operator[](from)); } } } private: basic_registry_type *owner; sigh_type construction; sigh_type destruction; sigh_type update; }; /** * @brief Mixin type used to add _reactive_ support to storage types. * @tparam Type Underlying storage type. * @tparam Registry Basic registry type. */ template class basic_reactive_mixin final: public Type { using underlying_type = Type; using owner_type = Registry; using alloc_traits = stl::allocator_traits; using basic_registry_type = basic_registry; using container_type = stl::vector>; static_assert(stl::is_base_of_v, "Invalid registry type"); [[nodiscard]] auto &owner_or_assert() const noexcept { ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); return static_cast(*owner); } void emplace_element(const Registry &, underlying_type::entity_type entity) { if(!underlying_type::contains(entity)) { underlying_type::emplace(entity); } } private: void bind_any(any value) noexcept final { owner = any_cast(&value); if constexpr(!stl::is_same_v) { if(owner == nullptr) { owner = any_cast(&value); } } underlying_type::bind_any(stl::move(value)); } public: /*! @brief Allocator type. */ using allocator_type = underlying_type::allocator_type; /*! @brief Underlying entity identifier. */ using entity_type = underlying_type::entity_type; /*! @brief Expected registry type. */ using registry_type = owner_type; /*! @brief Default constructor. */ basic_reactive_mixin() : basic_reactive_mixin{allocator_type{}} {} /** * @brief Constructs an empty storage with a given allocator. * @param allocator The allocator to use. */ explicit basic_reactive_mixin(const allocator_type &allocator) : underlying_type{allocator}, owner{}, conn{allocator} { } /*! @brief Default copy constructor, deleted on purpose. */ basic_reactive_mixin(const basic_reactive_mixin &) = delete; /** * @brief Move constructor. * @param other The instance to move from. */ basic_reactive_mixin(basic_reactive_mixin &&other) noexcept : underlying_type{static_cast(other)}, owner{other.owner}, conn{stl::move(other.conn)} { } /** * @brief Allocator-extended move constructor. * @param other The instance to move from. * @param allocator The allocator to use. */ basic_reactive_mixin(basic_reactive_mixin &&other, const allocator_type &allocator) : underlying_type{static_cast(other), allocator}, owner{other.owner}, conn{stl::move(other.conn), allocator} { } /*! @brief Default destructor. */ ~basic_reactive_mixin() override = default; /** * @brief Default copy assignment operator, deleted on purpose. * @return This mixin. */ basic_reactive_mixin &operator=(const basic_reactive_mixin &) = delete; /** * @brief Move assignment operator. * @param other The instance to move from. * @return This mixin. */ basic_reactive_mixin &operator=(basic_reactive_mixin &&other) noexcept { underlying_type::swap(other); return *this; } /** * @brief Makes storage _react_ to creation of objects of the given type. * @tparam Clazz Type of element to _react_ to. * @tparam Candidate Function to use to _react_ to the event. * @param id Optional name used to map the storage within the registry. * @return This mixin. */ template basic_reactive_mixin &on_construct(const id_type id = type_hash::value()) { auto curr = owner_or_assert().template storage(id).on_construct().template connect(*this); conn.push_back(stl::move(curr)); return *this; } /** * @brief Makes storage _react_ to update of objects of the given type. * @tparam Clazz Type of element to _react_ to. * @tparam Candidate Function to use to _react_ to the event. * @param id Optional name used to map the storage within the registry. * @return This mixin. */ template basic_reactive_mixin &on_update(const id_type id = type_hash::value()) { auto curr = owner_or_assert().template storage(id).on_update().template connect(*this); conn.push_back(stl::move(curr)); return *this; } /** * @brief Makes storage _react_ to destruction of objects of the given type. * @tparam Clazz Type of element to _react_ to. * @tparam Candidate Function to use to _react_ to the event. * @param id Optional name used to map the storage within the registry. * @return This mixin. */ template basic_reactive_mixin &on_destroy(const id_type id = type_hash::value()) { auto curr = owner_or_assert().template storage(id).on_destroy().template connect(*this); conn.push_back(stl::move(curr)); return *this; } /** * @brief Checks if a mixin refers to a valid registry. * @return True if the mixin refers to a valid registry, false otherwise. */ [[nodiscard]] explicit operator bool() const noexcept { return (owner != nullptr); } /** * @brief Returns a pointer to the underlying registry, if any. * @return A pointer to the underlying registry, if any. */ [[nodiscard]] const registry_type ®istry() const noexcept { return owner_or_assert(); } /*! @copydoc registry */ [[nodiscard]] registry_type ®istry() noexcept { return owner_or_assert(); } /** * @brief Returns a view that is filtered by the underlying storage. * @tparam Get Types of elements used to construct the view. * @tparam Exclude Types of elements used to filter the view. * @return A newly created view. */ template [[nodiscard]] basic_view...>, exclude_t...>> view(exclude_t = exclude_t{}) const { const owner_type &parent = owner_or_assert(); basic_view...>, exclude_t...>> elem{}; [&elem](const auto *...curr) { ((curr ? elem.storage(*curr) : void()), ...); }(parent.template storage>()..., parent.template storage>()..., this); return elem; } /*! @copydoc view */ template [[nodiscard]] basic_view...>, exclude_t...>> view(exclude_t = exclude_t{}) { stl::conditional_t<((stl::is_const_v && ...) && (stl::is_const_v && ...)), const owner_type, owner_type> &parent = owner_or_assert(); return {*this, parent.template storage>()..., parent.template storage>()...}; } /*! @brief Releases all connections to the underlying registry, if any. */ void reset() { for(auto &&curr: conn) { curr.release(); } conn.clear(); } private: basic_registry_type *owner; container_type conn; }; } // namespace entt #endif