diff --git a/CMakeLists.txt b/CMakeLists.txt index fdd01a356b..85dd1de45b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,6 +140,9 @@ if(ENTT_INCLUDE_HEADERS) $ $ $ + $ + $ + $ $ $ $ diff --git a/src/entt/entity/poly_storage_mixin.hpp b/src/entt/entity/poly_storage_mixin.hpp new file mode 100644 index 0000000000..4d833d3471 --- /dev/null +++ b/src/entt/entity/poly_storage_mixin.hpp @@ -0,0 +1,52 @@ +#ifndef ENTT_ENTITY_POLY_STORAGE_MIXIN_HPP +#define ENTT_ENTITY_POLY_STORAGE_MIXIN_HPP + +#include "storage.hpp" +#include "poly_type_traits.hpp" +#include "sigh_storage_mixin.hpp" + + +namespace entt { + +template +class poly_type; + +/** + * @brief Storage mixin for polymorphic component types + * @tparam Storage underlying storage type + * @tparam Entity entity type + * @tparam Type value type + */ +template +struct poly_storage_mixin : Storage { + /*! @brief Underlying value type. */ + using value_type = typename Storage::value_type; + /*! @brief Underlying entity identifier. */ + using entity_type = typename Storage::entity_type; + + /*! @brief Inherited constructors. */ + using Storage::Storage; + + /** + * @brief Forwards variables to mixins, if any. + * @param value A variable wrapped in an opaque container. + */ + void bind(any value) noexcept override { + if(auto *reg = any_cast>(&value); reg) { + bind_all_parent_types(*reg, poly_parent_types_t{}); + } + Storage::bind(std::move(value)); + } + +private: + template + void bind_all_parent_types(basic_registry& reg, [[maybe_unused]] type_list) { + (reg.ctx().template emplace>>().bind_storage(this), ...); + reg.ctx().template emplace>>().bind_storage(this); + } +}; + + +} // entt + +#endif diff --git a/src/entt/entity/poly_type_traits.hpp b/src/entt/entity/poly_type_traits.hpp new file mode 100644 index 0000000000..9dc4fb5498 --- /dev/null +++ b/src/entt/entity/poly_type_traits.hpp @@ -0,0 +1,171 @@ +#ifndef ENTT_ENTITY_POLY_TYPE_TRAITS_HPP +#define ENTT_ENTITY_POLY_TYPE_TRAITS_HPP + +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" + + +namespace entt { + +/** @brief Validates a polymorphic type and returns it unchanged */ +template +struct poly_type_validate; + +/** + * @brief Declares direct parent types of polymorphic component type. + * By default it uses the list from T::direct_parent_types, if it present, otherwise empty list is used. + * All parent types must be declared polymorphic. + * @code{.cpp} + * struct A {}; + * struct B : A {}; + * + * struct entt::poly_direct_parent_types { + * using parent_types = entt::type_list<>; // declares A as polymorphic type with no parents + * } + * struct entt::poly_direct_parent_types { + * using parent_types = entt::type_list; // declares B as polymorphic type with parent A + * } + * @endcode + * @tparam T polymorphic component type + */ +template +struct poly_direct_parent_types { + /** @brief entt::type_list of direct parent types */ + using parent_types = type_list<>; + + /** @brief used to detect, if this template was specialized for a type */ + using not_redefined_tag = void; +}; + +/** @copydoc poly_direct_parent_types */ +template +struct poly_direct_parent_types> { + using parent_types = typename T::direct_parent_types; +}; + +/** + * @brief For given polymorphic component type returns entt::type_list of direct parent types, + * for non polymorphic type will return empty list + * @tparam T type to get parents from + */ +template +using poly_direct_parent_types_t = typename poly_direct_parent_types::parent_types; + +/** + * @brief Declares list of all parent types of polymorphic component type. + * By default will concatenates list of all direct parent types and all lists of all parents for each parent type. All parent + * types must be declared polymorphic. + * @tparam T + */ +template +struct poly_parent_types { +private: + template + struct all_parent_types; + + template + struct all_parent_types> { + using types = type_list_cat_t, typename poly_parent_types::parent_types...>; + }; + +public: + /** @brief entt::type_list of all parent types */ + using parent_types = typename all_parent_types>::types; + + /** @brief used to detect, if this template was specialized for a type */ + using not_redefined_tag = void; +}; + +/** @copydoc poly_parent_types */ +template +struct poly_parent_types> { + using parent_types = typename T::all_parent_types; +}; + +/** + * @brief For given polymorphic component type returns entt::type_list of all parent types, + * for non polymorphic type will return empty list + * @tparam T type to get parents from + */ +template +using poly_parent_types_t = typename poly_parent_types::parent_types; + +/** + * Used to declare allocator type for polymorphic component type + * @tparam Type + */ +template +struct poly_type_allocator { + /** @brief allocator type */ + using type = std::allocator; +}; + +/** @copydoc poly_type_allocator */ +template +using poly_type_allocator_t = typename poly_type_allocator::type; + +/** + * @brief For a given type, detects, if it was declared polymorphic. Type considered polymorphic, if it was either:
+ * - inherited from entt::inherit
+ * - declared types direct_parent_types or all_parent_types
+ * - for this type there is specialization of either entt::poly_direct_parent_types or entt::poly_parent_types
+ * @tparam T type to check + */ +template +struct is_poly_type { + static constexpr bool value = true; +}; + +/** @copydoc is_poly_type */ +template +struct is_poly_type::not_redefined_tag, typename poly_parent_types::not_redefined_tag>> { + static constexpr bool value = false; +}; + +/** @copydoc is_poly_type */ +template +inline constexpr bool is_poly_type_v = is_poly_type::value; + +/** @copydoc poly_type_validate */ +template +struct poly_type_validate> { + static_assert(std::is_same_v, T>, "only decayed types allowed to be declared as polymorphic"); + static_assert(is_poly_type_v, "validating non-polymorphic type (probably some polymorphic type inherits type, that was not declared polymorphic)"); + static_assert(std::bool_constant<(is_poly_type_v && ...)>::value, "all parent types of a polymorphic type must be also polymorphic"); + static_assert(std::bool_constant<(!std::is_pointer_v> && ... && !std::is_pointer_v>)>::value, "double pointers are not allowed as a polymorphic components"); + static_assert(std::bool_constant<((std::is_pointer_v == std::is_pointer_v) && ...)>::value, "you cannot mix pointer-based and value-based polymorphic components inside one hierarchy"); + + /** @brief same input type, but validated */ + using type = T; +}; + +/** @copydoc poly_type_validate */ +template +using poly_type_validate_t = typename poly_type_validate>::type; + +/** + * @brief Returns if one polymorphic component type is same, or is declared as a parent of another polymorphic type + * @tparam Parent Parent type + * @tparam Type Child Type + */ +template +inline constexpr bool is_poly_parent_of_v = is_poly_type_v && (type_list_contains_v, Parent> || std::is_same_v); + +/** + * @brief Used to inherit from all given parent types and declare inheriting type polymorphic with given direct parents. + * All parent types are required to be polymorphic + * @code{.cpp} + * struct A : public entt::inherit<> {}; // base polymorphic type with no parents + * struct B : public entt::inherit
{}; // B inherits A, and declared as polymorphic component with direct parents {A} + * struct C : public entt::inherit {}; // C inherits B, and now has direct parents {B} and all parents {A, B} + * @endcode + * @tparam Parents list of parent types + */ +template +struct inherit : public poly_type_validate_t... { + using direct_parent_types = type_list; +}; + +} // entt + +#endif diff --git a/src/entt/entity/polymorphic.hpp b/src/entt/entity/polymorphic.hpp new file mode 100644 index 0000000000..0ab91b73b0 --- /dev/null +++ b/src/entt/entity/polymorphic.hpp @@ -0,0 +1,578 @@ +#ifndef ENTT_ENTITY_POLYMORPHIC_HPP +#define ENTT_ENTITY_POLYMORPHIC_HPP + +#include +#include +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" +#include "fwd.hpp" +#include "poly_type_traits.hpp" +#include "poly_storage_mixin.hpp" + + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace entt::internal { + +template +class poly_components_iterator { + using iterator_traits = typename std::iterator_traits; + +public: + using entity_type = typename PoolHolderType::entity_type; + using pointer = std::conditional_t, typename PoolHolderType::const_pointer_type, typename PoolHolderType::pointer_type>; + using value_type = std::conditional_t, pointer, constness_as_t>; + using reference = std::conditional_t, pointer, value_type&>; + using difference_type = typename iterator_traits::difference_type; + using iterator_category = std::forward_iterator_tag; + + poly_components_iterator() noexcept = default; + + poly_components_iterator(entity_type e, PoolsIterator pos, PoolsIterator last) noexcept : + ent(e), it(pos), end(last) { + if (it != end) { + current = static_cast(*it).try_get(ent); + if(current == nullptr) { + ++(*this); + } + } + } + + poly_components_iterator &operator++() noexcept { + ++it; + while (it != end) { + current = static_cast(*it).try_get(ent); + if (current != nullptr) + break; + ++it; + } + return *this; + } + + poly_components_iterator operator++(int) noexcept { + poly_components_iterator orig = *this; + return ++(*this), orig; + } + + bool operator==(const poly_components_iterator& other) const noexcept { + return it == other.it; + } + + bool operator!=(const poly_components_iterator& other) const noexcept { + return it != other.it; + } + + pointer operator->() noexcept { + return current; + } + + reference operator*() noexcept { + if constexpr(std::is_pointer_v) { + return operator->(); + } else { + return *operator->(); + } + } + +private: + entity_type ent; + PoolsIterator it; + PoolsIterator end; + pointer current = nullptr; +}; + + +template +struct basic_inlined_vector { + using value_type = T; + using iterator = T*; + using const_iterator = const T*; + + iterator begin() { + return std::visit([&] (auto& payload) -> iterator { + using type = std::decay_t; + if constexpr(std::is_same_v) { + return nullptr; + } else if constexpr(std::is_same_v) { + return std::addressof(payload); + } else if constexpr(std::is_same_v>) { + return std::addressof(*payload.begin()); + } + }, storage); + } + + const_iterator begin() const { + return const_cast*>(this)->begin(); + } + + iterator end() { + return std::visit([&] (auto& payload) -> iterator { + using type = std::decay_t; + if constexpr(std::is_same_v) { + return nullptr; + } else if constexpr(std::is_same_v) { + return std::addressof(payload) + 1; + } else if constexpr(std::is_same_v>) { + return std::addressof(*payload.begin()) + payload.size(); + } + }, storage); + } + + const_iterator end() const { + return const_cast*>(this)->end(); + } + + template + void emplace_back(Args&& ...args) { + auto& s = storage; + std::visit([&] (auto& payload) -> void { + using type = std::decay_t; + if constexpr(std::is_same_v) { + s.template emplace(std::forward(args)...); + } else if constexpr(std::is_same_v) { + std::vector v; + v.reserve(2); + v.emplace_back(std::move(payload)); + v.emplace_back(std::forward(args)...); + s.template emplace>(std::move(v)); + } else if constexpr(std::is_same_v>) { + payload.emplace_back(std::forward(args)...); + } + }, storage); + } + +private: + std::variant> storage; +}; + +} // namespace entt::internal + +/** + * Internal details not to be documented. + * @endcond + */ + + +namespace entt { + +/** @brief base class for entt::poly_pool_holder */ +template +class poly_pool_holder_base { +public: + inline poly_pool_holder_base(void* pool, + void* (*getter)(void*, Entity) noexcept) : + pool_ptr(pool), getter_ptr(getter) {} + +protected: + void* pool_ptr; + void* (*getter_ptr)(void*, Entity) noexcept; +}; + +/** + * @brief Holds pointer to child type storage and a function to convert it into parent type + * @tparam Entity underlying entity type + * @tparam Type polymorphic component type to convert into + * @tparam Allocator allocator type of the pool + */ +template +class poly_pool_holder : public poly_pool_holder_base { +public: + using poly_pool_holder_base::poly_pool_holder_base; + + /** @brief entity type of the component pools */ + using entity_type = Entity; + /** @brief component type */ + using value_type = Type; + /** @brief pointer to the component, will be same as value_type, if component itself is a pointer */ + using pointer_type = std::remove_pointer_t*; + /** @copydoc pointer_type */ + using const_pointer_type = const std::remove_pointer_t*; + /** @brief type of hold sparse set */ + using sparse_set_type = basic_sparse_set::template rebind_alloc>; + + /** + * @brief gets child from a child pool as parent type reference by given entity + * @param ptr pointer from the child type set + * @return Returns reference to Type, converted from a given pointer. Will return pointer instead of reference, if parent type is a pointer. + */ + inline pointer_type try_get(const Entity ent) noexcept { + return pointer_type(this->getter_ptr(this->pool_ptr, ent)); + } + + /** @copydoc try_get */ + inline const_pointer_type try_get(const Entity ent) const noexcept { + return const_cast*>(this)->try_get(ent); + } + + /** @brief returns underlying pool */ + inline auto& pool() noexcept { + return *static_cast(this->pool_ptr); + } + + /** @copydoc pool */ + inline const auto& pool() const noexcept { + return *static_cast(this->pool_ptr); + } + + /** + * @brief removes component from pool by entity + * @param ent entity + * @return true, if component was removed, false, if it didnt exist + */ + inline bool remove(const Entity ent) { + return pool().remove(ent); + } +}; + +/** + * @brief Holds runtime information about one polymorphic component type. + * @tparam Entity underlying entity type + * @tparam Type polymorphic component type +* @tparam Allocator allocator type for all bound contained pool holders + */ +template +class poly_type { + /** @brief derived value to base pointer conversion, workaround for if constexpr bug on some compilers */ + template + struct derived_to_base_ptr { + inline static Base* convert(Derived& ref) noexcept { + return static_cast(std::addressof(ref)); + } + }; + + /** @copydoc derived_to_base_ptr */ + template + struct derived_to_base_ptr>> { + inline static Base convert(Derived ptr) noexcept { + return static_cast(ptr); + } + }; + + /** @brief value to pointer conversion, workaround for if constexpr bug on some compilers */ + template + struct value_to_ptr { + inline static T* convert(T& ref) noexcept { + return std::addressof(ref); + } + }; + + /** @copydoc value_to_ptr */ + template + struct value_to_ptr>> { + inline static T convert(T ptr) noexcept { + return ptr; + } + }; + +public: + /** @brief entity type of the component pools */ + using entity_type = Entity; + /** @brief component type */ + using value_type = Type; + /** @brief allocator type */ + using allocator_type = Allocator; + /** @brief type of underlying container for bound pools */ + using pools_container = internal::basic_inlined_vector>; + /** @brief type of the pool holder */ + using pool_holder = poly_pool_holder; + /** @brief single entity component iterator type */ + using component_iterator = internal::poly_components_iterator; + /** @brief const single entity component iterator type */ + using const_component_iterator = internal::poly_components_iterator; + + /** + * @brief From a given set, makes a poly_storage_holder to hold child type and convert to parent type. + * @tparam ChildType source type to convert from + * @param pool_ptr child (source) type pool + * @return poly_storage_holder to hold ChildType set and access it as Type + */ + template + inline static pool_holder make_pool_holder(StorageType* pool_ptr) noexcept { + using BaseType = Type; + using DerivedType = typename StorageType::value_type; + static_assert(is_poly_type_v); + static_assert(type_list_contains_v, BaseType> || std::is_same_v); + static_assert(std::is_pointer_v == std::is_pointer_v); + static_assert(std::is_same_v::template rebind_traits::pointer, typename std::allocator_traits::pointer>, + "Allocator pointer types dont match for polymorphic types in one hierarchy. Use std::poly_type_allocator to explicitly provide allocator type for each polymorphic component type."); + + void* (*get)(void*, Entity entity) noexcept = +[](void* pool, const Entity entity) noexcept -> void* { + if constexpr(!ignore_as_empty_v) { + if(static_cast(pool)->contains(entity)) { + // if entity is contained within the set + if constexpr(std::is_base_of_v, std::remove_pointer_t>) { + // if base type is inherited from derived type, do pointer conversion + return derived_to_base_ptr::convert(static_cast(pool)->get(entity)); + } else { + // no inheritance - no conversion required, just get the pointer + return value_to_ptr::convert(static_cast(pool)->get(entity)); + } + } + } + // otherwise, return null + return nullptr; + }; + + return pool_holder(pool_ptr, get); + } + + /** @brief binds given storage pointer as a child type pool to this polymorphic type */ + template + void bind_storage(Storage* storage_ptr) { + bound_pools.emplace_back(make_pool_holder(storage_ptr)); + } + + /** @brief returns all pools bound for this type */ + [[nodiscard]] auto& get_bound_pools() noexcept { + return bound_pools; + } + + /** @copydoc get_bound_pools */ + [[nodiscard]] const auto& get_bound_pools() const noexcept { + return bound_pools; + } + + /** @brief calls given function for all child pool holders */ + template + void each_pool(Func func) { + for (auto& pool : bound_pools) { + func(static_cast(pool)); + } + } + + /** @copydoc each_pool */ + template + void each_pool(Func func) const { + for (auto& pool : bound_pools) { + func(static_cast(pool)); + } + } + + /** @brief returns an iterable to iterate through all polymorphic components, derived from this type, attached to a given entities */ + [[nodiscard]] iterable_adaptor each(const entity_type ent) { + auto begin = bound_pools.begin(); + auto end = bound_pools.end(); + return { component_iterator(ent, begin, end), component_iterator(ent, end, end) }; + } + + /** @copydoc each */ + [[nodiscard]] iterable_adaptor each(const entity_type ent) const { + auto begin = bound_pools.begin(); + auto end = bound_pools.end(); + return { const_component_iterator(ent, begin, end), const_component_iterator(ent, end, end) }; + } + +private: + pools_container bound_pools{}; +}; + + +/** + * @brief Provides access of runtime polymorphic type data from the underlying pools holder type. + * Allows user to create specializations to store polymorphic type data separately from the registry. + * + * The poly_types_accessor specialization must define:
+ * - Type& assure(PolyPoolsHolder&) method, it must assure and return instance of @b Type stored given @b PolyPoolsHolder
+ * - holder_type - same as PolyPoolsHolder
+ * - entity_type - underlying entity type
+ * - allocator_type - allocator type getter for given component type Type
+ * + * @tparam PolyPoolsHolder Underlying polymorphic types data holder + */ +template +struct poly_types_accessor; + +/** + * poly_types_accessor specialisation to access polymorphic data, stored in the registry context + * @tparam Entity + */ +template +struct poly_types_accessor> { + /** @brief Underlying polymorphic types data holder */ + using holder_type = basic_registry; + + /** @brief Underlying entity type */ + using entity_type = Entity; + + /** @brief Returns allocator type for Type */ + template + using allocator_type = poly_type_allocator_t; + + /** + * @brief Assures and returns instance of some type stored given polymorphic types data holder + * @tparam Type Default-constructed type to assure in the underlying data holder and return + * @param holder Polymorphic types data holder reference + * @return + */ + template + static inline decltype(auto) assure(basic_registry& holder) { + return holder.ctx().template emplace(); + } +}; + +/** + * @brief Converts const T* to T* const, used in assure_poly_type, because first one looks much more intuitive, + * when the second is identical to const T for value types, but for pointers + */ +template +using poly_type_sanitize_t = std::conditional_t, + std::conditional_t, + std::remove_const_t>* const, // (const?) T* const + constness_as_t>*, std::remove_pointer_t>> // (const?) T* + , T>; // (const?) T + +/** + * @brief Assures runtime polymorphic type data for a given component types in the given polymorphic data holder + * @tparam Component Polymorphic component type to assure + * @tparam PolyPoolsHolder Polymorphic types data holder type + * @param holder Polymorphic types data holder to operate on + * @return Reference to poly_type, keeps const qualifiers both for Component and PolyPoolsHolder + */ +template +[[nodiscard]] decltype(auto) assure_poly_type(PolyPoolsHolder& holder) { + using type = poly_type_sanitize_t; + using no_const_type = poly_type_validate_t>; + static_assert(is_poly_type_v, "must be a polymorphic type"); + + using accessor_type = poly_types_accessor; + using result_type = poly_type>; + result_type& result = accessor_type::template assure(const_cast&>(holder)); + if constexpr(std::is_const_v || std::is_const_v) { + return const_cast(result); + } else { + return result; + } +} + +} // namespace entt + + +namespace entt::algorithm { + +/** + * @brief For given polymorphic component type iterate over all child instances of it, attached to a given entity + * @tparam Component Polymorphic component type + * @param reg Registry, or any other polymorphic data holder to operate on + * @param entity Entity, to get components from + * @return Iterable to iterate each component + */ +template +[[nodiscard]] decltype(auto) poly_get_all(PolyPoolsHolder& reg, [[maybe_unused]] const Entity entity) { + return assure_poly_type(reg).each(entity); +} + +/** + * @brief For given polymorphic component type find and return any child instance of this type, attached to a given entity + * @tparam Component Polymorphic component type + * @param reg Registry, or any other polymorphic data holder to operate on + * @param entity Entity, to get components from + * @return Pointer to attached component or nullptr, if none attached. NOTE: will return pointer type polymorphic components by value (as a single pointer) + */ +template +[[nodiscard]] decltype(auto) poly_get_any(PolyPoolsHolder& reg, [[maybe_unused]] const Entity entity) { + auto all = poly_get_all(reg, entity); + return all.begin() != all.end() ? all.begin().operator->() : nullptr; +} + +/** + * @brief For given polymorphic component type remove all child instances of this type, attached to a given entity + * @tparam Component Polymorphic component type + * @param reg Registry, or any other polymorphic data holder to operate on + * @param entity Entity, to remove components from + * @return Count of removed components + */ +template +int poly_remove(PolyPoolsHolder& reg, [[maybe_unused]] const Entity entity) { + int removed_count = 0; + assure_poly_type(reg).each_pool([&](auto& pool){ + removed_count += static_cast(pool.remove(entity)); + }); + return removed_count; +} + +/** +* @brief For given polymorphic component type count all child instances of this type, attached to a given entity +* @tparam Component Polymorphic component type +* @param reg Registry, or any other polymorphic data holder to operate on +* @param entity Entity, to count attached components +* @return Count of components, attached to a given entity + */ +template +size_t poly_count(PolyPoolsHolder& reg, [[maybe_unused]] const Entity entity) { + size_t count = 0; + assure_poly_type(reg).each_pool([&](auto& pool){ + count += static_cast(pool.pool().contains(entity)); + }); + return count; +} + +/** +* @brief For given polymorphic component type count all child instances of this type +* @tparam Component Polymorphic component type +* @param reg Registry, or any other polymorphic data holder to operate on +* @return Count of components + */ +template +size_t poly_count(PolyPoolsHolder& reg) { + size_t count = 0; + assure_poly_type(reg).each_pool([&](auto& pool){ + count += pool.pool().size(); + }); + return count; +} + +/** + * @brief For a given component type applies given func to all child instances of this type in registry. + * @tparam Component Polymorphic component type + * @tparam PolyPoolsHolder Entity type of underlying registry + * @tparam Entity Entity type of underlying registry + * @tparam Func Type of given function + * @param reg Registry, or any other polymorphic data holder to operate on + * @param func Function to call for each component, parameters are (entity, component&) or only one of them, or even none. + * Note: for pointer type components (T*) the component parameter is its value (T*) instead of pointer to value (T**) + */ +template +void poly_each(PolyPoolsHolder& reg, Func func) { + assure_poly_type(reg).each_pool([&](auto& pool){ + for (auto& ent : pool.pool()) { + if constexpr(std::is_invocable_v) { + auto ptr = pool.try_get(ent); + if constexpr(std::is_pointer_v) { + func(ent, ptr); + } else { + func(ent, *ptr); + } + } else if constexpr(std::is_invocable_v) { + auto ptr = pool.try_get(ent); + if constexpr(std::is_pointer_v) { + func(ptr); + } else { + func(*ptr); + } + } else if constexpr(std::is_invocable_v) { + func(ent); + } else { + func(); + } + } + }); +} + +} // namespace entt::algorithm + + +/** + * @brief storage_type template specialization for polymorphic component types + * @tparam Entity entity type + * @tparam Type polymorphic component type + */ +template +struct entt::storage_type>> { + using type = sigh_storage_mixin, Entity, poly_type_allocator_t>>>; +}; + + +#endif diff --git a/src/entt/entt.hpp b/src/entt/entt.hpp index 01c54f4157..c170fbeb57 100644 --- a/src/entt/entt.hpp +++ b/src/entt/entt.hpp @@ -25,6 +25,7 @@ #include "entity/helper.hpp" #include "entity/observer.hpp" #include "entity/organizer.hpp" +#include "entity/polymorphic.hpp" #include "entity/registry.hpp" #include "entity/runtime_view.hpp" #include "entity/sigh_storage_mixin.hpp" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a99436fc9b..1f6de48d65 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -209,6 +209,8 @@ SETUP_BASIC_TEST(handle entt/entity/handle.cpp) SETUP_BASIC_TEST(helper entt/entity/helper.cpp) SETUP_BASIC_TEST(observer entt/entity/observer.cpp) SETUP_BASIC_TEST(organizer entt/entity/organizer.cpp) +SETUP_BASIC_TEST(poly_type_traits entt/entity/poly_type_traits.cpp) +SETUP_BASIC_TEST(polymorphic entt/entity/polymorphic.cpp) SETUP_BASIC_TEST(registry entt/entity/registry.cpp) SETUP_BASIC_TEST(runtime_view entt/entity/runtime_view.cpp) SETUP_BASIC_TEST(sigh_storage_mixin entt/entity/sigh_storage_mixin.cpp) diff --git a/test/entt/common/polymorphic_type.hpp b/test/entt/common/polymorphic_type.hpp new file mode 100644 index 0000000000..e7483404c0 --- /dev/null +++ b/test/entt/common/polymorphic_type.hpp @@ -0,0 +1,72 @@ +#ifndef ENTT_COMMON_POLYMORPHIC_TYPE_HPP +#define ENTT_COMMON_POLYMORPHIC_TYPE_HPP + +#include + + +struct animal : public entt::inherit<> { virtual std::string name() = 0; int animal_payload{}; virtual ~animal() = default; }; +struct dog : public entt::inherit { std::string name() override { return "dog"; }; }; +struct cat : public entt::inherit { std::string name() override { return "cat"; }; }; + +struct shape { virtual std::string draw() = 0; int shape_payload{}; virtual ~shape() = default; }; +struct sphere : public shape { std::string draw() override { return "sphere"; } }; +struct cube : public shape { std::string draw() override { return "cube"; } }; + +template<> +struct entt::poly_direct_parent_types { + using parent_types = type_list<>; +}; + +template<> +struct entt::poly_direct_parent_types { + using parent_types = type_list; +}; + +template<> +struct entt::poly_direct_parent_types { + using parent_types = type_list; +}; + +struct fat_cat : public entt::inherit {}; + + +template<> +struct entt::poly_direct_parent_types { + using parent_types = type_list<>; +}; + +template<> +struct entt::poly_direct_parent_types { + using parent_types = type_list; +}; + +template<> +struct entt::poly_direct_parent_types { + using parent_types = type_list; +}; + +template<> +struct entt::poly_direct_parent_types { + using parent_types = type_list<>; +}; + +template<> +struct entt::poly_direct_parent_types { + using parent_types = type_list; +}; + +template<> +struct entt::poly_direct_parent_types { + using parent_types = type_list; +}; + +template<> +struct entt::poly_direct_parent_types { + using parent_types = type_list; +}; + + +struct not_poly_type_base {}; +struct not_poly_type : public not_poly_type_base {}; + +#endif diff --git a/test/entt/entity/poly_type_traits.cpp b/test/entt/entity/poly_type_traits.cpp new file mode 100644 index 0000000000..502eb3821a --- /dev/null +++ b/test/entt/entity/poly_type_traits.cpp @@ -0,0 +1,70 @@ +#include +#include "../common/polymorphic_type.hpp" + + +TEST(IsPolyType, Functionalities) { + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + ASSERT_TRUE(entt::is_poly_type_v); + + ASSERT_FALSE(entt::is_poly_type_v); + ASSERT_FALSE(entt::is_poly_type_v); + ASSERT_FALSE(entt::is_poly_type_v); +} + +TEST(ValidatePolyType, Functionalities) { + ASSERT_TRUE((std::is_same_v, animal>)); + ASSERT_TRUE((std::is_same_v, dog>)); + ASSERT_TRUE((std::is_same_v, cat>)); + ASSERT_TRUE((std::is_same_v, shape>)); + ASSERT_TRUE((std::is_same_v, sphere>)); + ASSERT_TRUE((std::is_same_v, cube>)); + ASSERT_TRUE((std::is_same_v, fat_cat>)); +} + +TEST(IsPolyParentOf, Functionalities) { + ASSERT_TRUE((entt::is_poly_parent_of_v)); + ASSERT_TRUE((entt::is_poly_parent_of_v)); + ASSERT_TRUE((entt::is_poly_parent_of_v)); + ASSERT_TRUE((entt::is_poly_parent_of_v)); + ASSERT_TRUE((entt::is_poly_parent_of_v)); + ASSERT_TRUE((entt::is_poly_parent_of_v)); + ASSERT_TRUE((entt::is_poly_parent_of_v)); + ASSERT_TRUE((entt::is_poly_parent_of_v)); + + ASSERT_FALSE((entt::is_poly_parent_of_v)); + ASSERT_FALSE((entt::is_poly_parent_of_v)); + ASSERT_FALSE((entt::is_poly_parent_of_v)); + ASSERT_FALSE((entt::is_poly_parent_of_v)); + ASSERT_FALSE((entt::is_poly_parent_of_v)); + ASSERT_FALSE((entt::is_poly_parent_of_v)); +} + +TEST(ParentTypeList, Functionalities) { + ASSERT_TRUE((std::is_same_v, entt::type_list<>>)); + ASSERT_TRUE((std::is_same_v, entt::type_list>)); + ASSERT_TRUE((std::is_same_v, entt::type_list>)); + ASSERT_TRUE((std::is_same_v, entt::type_list<>>)); + ASSERT_TRUE((std::is_same_v, entt::type_list>)); + ASSERT_TRUE((std::is_same_v, entt::type_list>)); +} + +TEST(SanitizePolyType, Functionalities) { + ASSERT_TRUE((std::is_same_v, cat>)); + ASSERT_TRUE((std::is_same_v, const cat>)); + ASSERT_TRUE((std::is_same_v, cat*>)); + ASSERT_TRUE((std::is_same_v, cat* const>)); + ASSERT_TRUE((std::is_same_v, cat* const>)); + ASSERT_TRUE((std::is_same_v, cat* const>)); +} diff --git a/test/entt/entity/polymorphic.cpp b/test/entt/entity/polymorphic.cpp new file mode 100644 index 0000000000..e45bb8be0c --- /dev/null +++ b/test/entt/entity/polymorphic.cpp @@ -0,0 +1,425 @@ +#include +#include +#include "../common/polymorphic_type.hpp" + + +TEST(PolyGetAny, Functionalities) { + entt::registry reg; + + entt::entity entity1 = reg.create(); // components: cat, dog + entt::entity entity2 = reg.create(); // components: dog, sphere, cube + entt::entity entity3 = reg.create(); // components: fat_cat + reg.emplace(entity1); + reg.emplace(entity1); + reg.emplace(entity2); + reg.emplace(entity2); + reg.emplace(entity2); + reg.emplace(entity3); + + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity1), nullptr); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity1), reg.try_get(entity1)); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity2), reg.try_get(entity2)); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity2), reg.try_get(entity2)); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity3), reg.try_get(entity3)); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity3)->draw(), "sphere"); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity3)->name(), "cat"); + + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity1), nullptr); +} + + +TEST(PolyGetAnyPointer, Functionalities) { + entt::registry reg; + + auto cat1 = std::make_unique(); + auto dog1 = std::make_unique(); + auto dog2 = std::make_unique(); + auto cube2 = std::make_unique(); + auto sphere2 = std::make_unique(); + auto fat_cat3 = std::make_unique(); + + entt::entity entity1 = reg.create(); // components: cat, dog + entt::entity entity2 = reg.create(); // components: dog, sphere, cube + entt::entity entity3 = reg.create(); // components: fat_cat + reg.emplace(entity1, cat1.get()); + reg.emplace(entity1, dog1.get()); + reg.emplace(entity2, dog2.get()); + reg.emplace(entity2, cube2.get()); + reg.emplace(entity2, sphere2.get()); + reg.emplace(entity3, fat_cat3.get()); + + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity1), nullptr); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity1), reg.get(entity1)); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity2), reg.get(entity2)); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity2), reg.get(entity2)); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity3), reg.get(entity3)); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity3)->draw(), "sphere"); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity3)->name(), "cat"); + + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity1), nullptr); +} + + +TEST(PolyGetAll, Functionalities) { + entt::registry reg; + + entt::entity entity1 = reg.create(); // components: cat(payload=1), dog(payload=2) + entt::entity entity2 = reg.create(); // components: dog(payload=2), cube(payload=3), sphere(payload=4) + entt::entity entity3 = reg.create(); // components: fat_cat(both payloads = 5) + reg.emplace(entity1).animal_payload = 1; + reg.emplace(entity1).animal_payload = 2; + reg.emplace(entity2).animal_payload = 2; + reg.emplace(entity2).shape_payload = 3; + reg.emplace(entity2).shape_payload = 4; + auto& c = reg.emplace(entity3); + c.animal_payload = c.shape_payload = 5; + + { + int count = 0; + for(animal& a: entt::algorithm::poly_get_all(reg, entity1)) { + ASSERT_TRUE(a.animal_payload == 1 || a.animal_payload == 2); + ASSERT_TRUE(a.animal_payload != 1 || a.name() == "cat"); + ASSERT_TRUE(a.animal_payload != 2 || a.name() == "dog"); + count++; + } + ASSERT_EQ(count, 2); + } + + { + int count = 0; + for (cat& a: entt::algorithm::poly_get_all(reg, entity1)) { + ASSERT_TRUE(a.animal_payload == 1 && a.name() == "cat"); + count++; + } + ASSERT_EQ(count, 1); + } + + { + for (shape& s: entt::algorithm::poly_get_all(reg, entity1)) { FAIL() << std::addressof(s); } + } + + { + int count = 0; + for(shape& s: entt::algorithm::poly_get_all(reg, entity2)) { + ASSERT_TRUE(s.shape_payload == 3 || s.shape_payload == 4); + ASSERT_TRUE(s.shape_payload != 3 || s.draw() == "cube"); + ASSERT_TRUE(s.shape_payload != 4 || s.draw() == "sphere"); + count++; + } + ASSERT_EQ(count, 2); + } + + { + int animal_count = 0; + for(animal& a: entt::algorithm::poly_get_all(reg, entity3)) { + ASSERT_TRUE(a.animal_payload == 5 && a.name() == "cat"); + animal_count++; + } + ASSERT_EQ(animal_count, 1); + int shape_count = 0; + for(shape& s: entt::algorithm::poly_get_all(reg, entity3)) { + ASSERT_TRUE(s.shape_payload == 5 && s.draw() == "sphere"); + shape_count++; + } + ASSERT_EQ(shape_count, 1); + } +} + + +TEST(PolyGetAllPointers, Functionalities) { + entt::registry reg; + + auto cat1 = std::make_unique(); + auto dog1 = std::make_unique(); + auto dog2 = std::make_unique(); + auto cube2 = std::make_unique(); + auto sphere2 = std::make_unique(); + auto fat_cat3 = std::make_unique(); + + entt::entity entity1 = reg.create(); // components: cat(payload=1), dog(payload=2) + entt::entity entity2 = reg.create(); // components: dog(payload=2), cube(payload=3), sphere(payload=4) + entt::entity entity3 = reg.create(); // components: fat_cat(both payloads = 5) + reg.emplace(entity1, cat1.get())->animal_payload = 1; + reg.emplace(entity1, dog1.get())->animal_payload = 2; + reg.emplace(entity2, dog2.get())->animal_payload = 2; + reg.emplace(entity2, cube2.get())->shape_payload = 3; + reg.emplace(entity2, sphere2.get())->shape_payload = 4; + auto* c = reg.emplace(entity3, fat_cat3.get()); + c->animal_payload = c->shape_payload = 5; + + { + int count = 0; + for(animal* a: entt::algorithm::poly_get_all(reg, entity1)) { + ASSERT_TRUE(a->animal_payload == 1 || a->animal_payload == 2); + ASSERT_TRUE(a->animal_payload != 1 || a->name() == "cat"); + ASSERT_TRUE(a->animal_payload != 2 || a->name() == "dog"); + count++; + } + ASSERT_EQ(count, 2); + } + + { + int count = 0; + for (cat* a: entt::algorithm::poly_get_all(reg, entity1)) { + ASSERT_TRUE(a->animal_payload == 1 && a->name() == "cat"); + count++; + } + ASSERT_EQ(count, 1); + } + + { + for (shape* s: entt::algorithm::poly_get_all(reg, entity1)) { FAIL() << s; } + } + + { + int count = 0; + for(shape* s: entt::algorithm::poly_get_all(reg, entity2)) { + ASSERT_TRUE(s->shape_payload == 3 || s->shape_payload == 4); + ASSERT_TRUE(s->shape_payload != 3 || s->draw() == "cube"); + ASSERT_TRUE(s->shape_payload != 4 || s->draw() == "sphere"); + count++; + } + ASSERT_EQ(count, 2); + } + + { + int animal_count = 0; + for(animal* a: entt::algorithm::poly_get_all(reg, entity3)) { + ASSERT_TRUE(a->animal_payload == 5 && a->name() == "cat"); + animal_count++; + } + ASSERT_EQ(animal_count, 1); + int shape_count = 0; + for(shape* s: entt::algorithm::poly_get_all(reg, entity3)) { + ASSERT_TRUE(s->shape_payload == 5 && s->draw() == "sphere"); + shape_count++; + } + ASSERT_EQ(shape_count, 1); + } +} + + +TEST(PolyCount, Functionalities) { + entt::registry reg; + + entt::entity entity1 = reg.create(); // components: cat, dog + entt::entity entity2 = reg.create(); // components: dog, sphere, cube + entt::entity entity3 = reg.create(); // components: fat_cat + reg.emplace(entity1); + reg.emplace(entity1); + reg.emplace(entity2); + reg.emplace(entity2); + reg.emplace(entity2); + reg.emplace(entity3); + + ASSERT_EQ(entt::algorithm::poly_count(reg, entity1), 1); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity1), 1); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity1), 2); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity1), 0); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity2), 0); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity2), 1); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity2), 2); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity3), 1); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity3), 1); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity3), 1); + + ASSERT_EQ(entt::algorithm::poly_count(reg), 1); + ASSERT_EQ(entt::algorithm::poly_count(reg), 2); + ASSERT_EQ(entt::algorithm::poly_count(reg), 2); + ASSERT_EQ(entt::algorithm::poly_count(reg), 2); + ASSERT_EQ(entt::algorithm::poly_count(reg), 4); + ASSERT_EQ(entt::algorithm::poly_count(reg), 3); + ASSERT_EQ(entt::algorithm::poly_count(reg), 1); +} + + +TEST(PolyEach, Functionalities) { + entt::registry reg; + + entt::entity entity1 = reg.create(); // components: cat(payload=1), dog(payload=2) + entt::entity entity2 = reg.create(); // components: dog(payload=2), cube(payload=3), sphere(payload=4) + entt::entity entity3 = reg.create(); // components: fat_cat(both payloads = 5) + reg.emplace(entity1).animal_payload = 1; + reg.emplace(entity1).animal_payload = 2; + reg.emplace(entity2).animal_payload = 2; + reg.emplace(entity2).shape_payload = 3; + reg.emplace(entity2).shape_payload = 4; + auto& c = reg.emplace(entity3); + c.animal_payload = c.shape_payload = 5; + + { + int count = 0; + entt::algorithm::poly_each(reg, [&] (entt::entity e, cube& s) -> void { + ASSERT_TRUE(e == entity2); + ASSERT_TRUE(s.shape_payload == 3); + count++; + }); + ASSERT_EQ(count, 1); + } + + { + int count = 0; + entt::algorithm::poly_each(reg, [&] (entt::entity e, animal& a) -> void { + ASSERT_TRUE(e == entity1 || e == entity2 || e == entity3); + if (e == entity1) { + ASSERT_TRUE(a.animal_payload == 1 || a.animal_payload == 2); + ASSERT_TRUE(a.animal_payload != 1 || a.name() == "cat"); + ASSERT_TRUE(a.animal_payload != 2 || a.name() == "dog"); + } else if (e == entity2) { + ASSERT_TRUE(a.animal_payload == 2 && a.name() == "dog"); + } else if (e == entity3) { + ASSERT_TRUE(a.animal_payload == 5 && a.name() == "cat"); + } + count++; + }); + ASSERT_EQ(count, 4); + } + + { + int count = 0; + entt::algorithm::poly_each(reg, [&] (entt::entity e, shape& s) -> void { + ASSERT_TRUE(e == entity2 || e == entity3); + if (e == entity2) { + ASSERT_TRUE(s.shape_payload == 3 || s.shape_payload == 4); + ASSERT_TRUE(s.shape_payload != 3 || s.draw() == "cube"); + ASSERT_TRUE(s.shape_payload != 4 || s.draw() == "sphere"); + } else if (e == entity3) { + ASSERT_TRUE(s.shape_payload == 5 && s.draw() == "sphere"); + } + count++; + }); + ASSERT_EQ(count, 3); + } + + { + entt::algorithm::poly_each(reg, [&] (entt::entity e, [[maybe_unused]] animal* a) -> void { FAIL(); }); + } +} + + +TEST(PolyEachPointer, Functionalities) { + entt::registry reg; + + auto cat1 = std::make_unique(); + auto dog1 = std::make_unique(); + auto dog2 = std::make_unique(); + auto cube2 = std::make_unique(); + auto sphere2 = std::make_unique(); + auto fat_cat3 = std::make_unique(); + + entt::entity entity1 = reg.create(); // components: cat(payload=1), dog(payload=2) + entt::entity entity2 = reg.create(); // components: dog(payload=2), cube(payload=3), sphere(payload=4) + entt::entity entity3 = reg.create(); // components: fat_cat(both payloads = 5) + reg.emplace(entity1, cat1.get())->animal_payload = 1; + reg.emplace(entity1, dog1.get())->animal_payload = 2; + reg.emplace(entity2, dog2.get())->animal_payload = 2; + reg.emplace(entity2, cube2.get())->shape_payload = 3; + reg.emplace(entity2, sphere2.get())->shape_payload = 4; + auto* c = reg.emplace(entity3, fat_cat3.get()); + c->animal_payload = c->shape_payload = 5; + + { + int count = 0; + entt::algorithm::poly_each(reg, [&] (entt::entity e, cube* s) -> void { + ASSERT_TRUE(e == entity2); + ASSERT_TRUE(s->shape_payload == 3); + count++; + }); + ASSERT_EQ(count, 1); + } + + { + int count = 0; + entt::algorithm::poly_each(reg, [&] (entt::entity e, animal* a) -> void { + ASSERT_TRUE(e == entity1 || e == entity2 || e == entity3); + if (e == entity1) { + ASSERT_TRUE(a->animal_payload == 1 || a->animal_payload == 2); + ASSERT_TRUE(a->animal_payload != 1 || a->name() == "cat"); + ASSERT_TRUE(a->animal_payload != 2 || a->name() == "dog"); + } else if (e == entity2) { + ASSERT_TRUE(a->animal_payload == 2 && a->name() == "dog"); + } else if (e == entity3) { + ASSERT_TRUE(a->animal_payload == 5 && a->name() == "cat"); + } + count++; + }); + ASSERT_EQ(count, 4); + } + + { + int count = 0; + entt::algorithm::poly_each(reg, [&] (entt::entity e, shape* s) -> void { + ASSERT_TRUE(e == entity2 || e == entity3); + if (e == entity2) { + ASSERT_TRUE(s->shape_payload == 3 || s->shape_payload == 4); + ASSERT_TRUE(s->shape_payload != 3 || s->draw() == "cube"); + ASSERT_TRUE(s->shape_payload != 4 || s->draw() == "sphere"); + } else if (e == entity3) { + ASSERT_TRUE(s->shape_payload == 5 && s->draw() == "sphere"); + } + count++; + }); + ASSERT_EQ(count, 3); + } + + { + entt::algorithm::poly_each(reg, [&] (entt::entity e, [[maybe_unused]] animal& a) -> void { FAIL(); }); + } +} + + +TEST(PolyRemove, Functionalities) { + entt::registry reg; + + entt::entity entity1 = reg.create(); // components: cat, dog + entt::entity entity2 = reg.create(); // components: dog, sphere, cube + reg.emplace(entity1); + reg.emplace(entity1); + reg.emplace(entity2); + reg.emplace(entity2); + reg.emplace(entity2); + + ASSERT_NE(reg.try_get(entity1), nullptr); + ASSERT_NE(entt::algorithm::poly_get_any(reg, entity1), nullptr); + ASSERT_NE(reg.try_get(entity1), nullptr); + ASSERT_NE(entt::algorithm::poly_get_any(reg, entity1), nullptr); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity1), 2); + entt::algorithm::poly_remove(reg, entity1); + ASSERT_EQ(reg.try_get(entity1), nullptr); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity1), nullptr); + ASSERT_EQ(reg.try_get(entity1), nullptr); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity1), nullptr); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity1), 0); + + ASSERT_NE(reg.try_get(entity2), nullptr); + ASSERT_NE(entt::algorithm::poly_get_any(reg, entity2), nullptr); + ASSERT_NE(reg.try_get(entity2), nullptr); + ASSERT_NE(entt::algorithm::poly_get_any(reg, entity2), nullptr); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity2), 2); + entt::algorithm::poly_remove(reg, entity2); + ASSERT_EQ(reg.try_get(entity2), nullptr); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity2), nullptr); + ASSERT_NE(reg.try_get(entity2), nullptr); + ASSERT_NE(entt::algorithm::poly_get_any(reg, entity2), nullptr); + ASSERT_EQ(entt::algorithm::poly_count(reg, entity2), 1); + + entt::entity entity3 = reg.create(); // components: fat_cat + + reg.emplace(entity3); + ASSERT_NE(reg.try_get(entity3), nullptr); + ASSERT_NE(entt::algorithm::poly_get_any(reg, entity3), nullptr); + ASSERT_NE(entt::algorithm::poly_get_any(reg, entity3), nullptr); + entt::algorithm::poly_remove(reg, entity3); + ASSERT_EQ(reg.try_get(entity3), nullptr); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity3), nullptr); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity3), nullptr); + + reg.emplace(entity3); + ASSERT_NE(reg.try_get(entity3), nullptr); + ASSERT_NE(entt::algorithm::poly_get_any(reg, entity3), nullptr); + ASSERT_NE(entt::algorithm::poly_get_any(reg, entity3), nullptr); + entt::algorithm::poly_remove(reg, entity3); + ASSERT_EQ(reg.try_get(entity3), nullptr); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity3), nullptr); + ASSERT_EQ(entt::algorithm::poly_get_any(reg, entity3), nullptr); +} \ No newline at end of file