From 3498dea486c2addea91fb4e95a319f8b16915b1b Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Fri, 17 Dec 2021 16:46:52 +0100 Subject: [PATCH] registry (close #521): * remove visit * added storage_proxy_iterator (input iterator category, modeled as a random access iterator) * added ::storage() (const and non-const) to return an iterable object over all pools --- docs/md/entity.md | 6 +- src/entt/entity/handle.hpp | 17 +++- src/entt/entity/registry.hpp | 176 +++++++++++++++++++++++++++++----- test/entt/entity/handle.cpp | 5 +- test/entt/entity/registry.cpp | 102 ++++++++++++++++---- test/example/entity_copy.cpp | 16 ++-- 6 files changed, 264 insertions(+), 58 deletions(-) diff --git a/docs/md/entity.md b/docs/md/entity.md index 72357e5f9..d80ba2f1d 100644 --- a/docs/md/entity.md +++ b/docs/md/entity.md @@ -1208,11 +1208,11 @@ them by copy if needed: ```cpp // create a copy of an entity component by component -registry.visit([entity, other](auto &&storage) { - if(storage.contains(entity)) { +for(auto &&curr: registry.storage()) { + if(auto &storage = curr.second; storage.contains(entity)) { storage.emplace(other, storage.get(entity)); } -}); +} ``` This is particularly useful to clone entities in an opaque way. In addition, the diff --git a/src/entt/entity/handle.hpp b/src/entt/entity/handle.hpp index 91ddf864a..093d2af61 100644 --- a/src/entt/entity/handle.hpp +++ b/src/entt/entity/handle.hpp @@ -266,17 +266,26 @@ struct basic_handle { /** * @brief Visits a handle and returns the pools for its components. - * @sa basic_registry::visit + * + * The signature of the function should be equivalent to the following: + * + * @code{.cpp} + * void(id_type, const basic_sparse_set &); + * @endcode + * + * Returned pools are those that contain the entity associated with the + * handle. + * * @tparam Func Type of the function object to invoke. * @param func A valid function object. */ template void visit(Func &&func) const { - reg->visit([func = std::forward(func), this](auto &&storage) { + for(auto [id, storage]: reg->storage()) { if(storage.contains(entt)) { - func(storage); + func(id, storage); } - }); + } } private: diff --git a/src/entt/entity/registry.hpp b/src/entt/entity/registry.hpp index c947390cd..b4a0ce6f4 100644 --- a/src/entt/entity/registry.hpp +++ b/src/entt/entity/registry.hpp @@ -14,6 +14,7 @@ #include "../core/algorithm.hpp" #include "../core/any.hpp" #include "../core/fwd.hpp" +#include "../core/iterator.hpp" #include "../core/type_info.hpp" #include "../core/type_traits.hpp" #include "../core/utility.hpp" @@ -29,6 +30,141 @@ namespace entt { +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class storage_proxy_iterator { + template + friend class storage_proxy_iterator; + + using iterator_traits = std::iterator_traits; + using first_type = typename iterator_traits::value_type::first_type; + using second_type = typename iterator_traits::value_type::second_type::element_type; + +public: + using value_type = std::pair> &>; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = typename iterator_traits::difference_type; + using iterator_category = std::input_iterator_tag; + + storage_proxy_iterator() ENTT_NOEXCEPT = default; + + storage_proxy_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + storage_proxy_iterator(const storage_proxy_iterator &other) + : it{other.it} {} + + storage_proxy_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + storage_proxy_iterator operator++(int) ENTT_NOEXCEPT { + storage_proxy_iterator orig = *this; + return ++(*this), orig; + } + + storage_proxy_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + storage_proxy_iterator operator--(int) ENTT_NOEXCEPT { + storage_proxy_iterator orig = *this; + return operator--(), orig; + } + + storage_proxy_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + storage_proxy_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + storage_proxy_iterator copy = *this; + return (copy += value); + } + + storage_proxy_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + storage_proxy_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const { + return {it[value].first, *it[value].second}; + } + + [[nodiscard]] reference operator*() const { + return {it->first, *it->second}; + } + + [[nodiscard]] pointer operator->() const { + return operator*(); + } + + template + friend auto operator-(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] auto operator-(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + /** * @brief Fast and reliable entity-component system. * @tparam Entity A valid entity type (see entt_traits for more details). @@ -181,6 +317,23 @@ public: return *this; } + /** + * @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() ENTT_NOEXCEPT { + return iterable_adaptor{internal::storage_proxy_iterator{pools.begin()}, internal::storage_proxy_iterator{pools.end()}}; + } + + /*! @copydoc storage */ + [[nodiscard]] auto storage() const ENTT_NOEXCEPT { + return iterable_adaptor{internal::storage_proxy_iterator{pools.cbegin()}, internal::storage_proxy_iterator{pools.cend()}}; + } + /** * @brief Returns the storage for a given component type. * @tparam Component Type of component of which to return the storage. @@ -583,7 +736,7 @@ public: /** * @brief Patches the given component for an entity. * - * The signature of the functions should be equivalent to the following: + * The signature of the function should be equivalent to the following: * * @code{.cpp} * void(Component &); @@ -1266,27 +1419,6 @@ public: assure().respect(assure()); } - /** - * @brief Visits a registry and returns the pools for its components. - * - * The signature of the function should be equivalent to the following: - * - * @code{.cpp} - * void(const basic_sparse_set &); - * @endcode - * - * Returned pools are those of the components managed by the registry. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - void visit(Func func) const { - for(auto &&curr: pools) { - func(*curr.second); - } - } - /** * @brief Binds an object to the context of the registry. * diff --git a/test/entt/entity/handle.cpp b/test/entt/entity/handle.cpp index 228d7b7f0..190bc552f 100644 --- a/test/entt/entity/handle.cpp +++ b/test/entt/entity/handle.cpp @@ -169,7 +169,10 @@ TEST(BasicHandle, Component) { ASSERT_TRUE(registry.storage().empty()); ASSERT_EQ(0u, (handle.remove())); - handle.visit([](const auto &pool) { ASSERT_EQ(entt::type_id(), pool.type()); }); + handle.visit([&handle](const auto id, const auto &pool) { + ASSERT_EQ(id, entt::type_id().hash()); + ASSERT_TRUE(pool.contains(handle.entity())); + }); ASSERT_TRUE((handle.any_of())); ASSERT_FALSE((handle.all_of())); diff --git a/test/entt/entity/registry.cpp b/test/entt/entity/registry.cpp index 31c7e372f..1c63724d6 100644 --- a/test/entt/entity/registry.cpp +++ b/test/entt/entity/registry.cpp @@ -1854,26 +1854,6 @@ TEST(Registry, AssignEntities) { ASSERT_EQ(traits_type::to_entity(other.create()), traits_type::to_integral(entities[1])); } -TEST(Registry, Visit) { - entt::registry registry; - const auto entity = registry.create(); - const auto other = registry.create(); - - registry.emplace(entity); - registry.emplace(other); - registry.emplace(entity); - - bool hasType[3]{}; - - registry.visit([&hasType](const auto &pool) { - hasType[0] = hasType[0] || (pool.type() == entt::type_id()); - hasType[1] = hasType[1] || (pool.type() == entt::type_id()); - hasType[2] = hasType[2] || (pool.type() == entt::type_id()); - }); - - ASSERT_TRUE(hasType[0] && hasType[1] && hasType[2]); -} - TEST(Registry, ScramblingPoolsIsAllowed) { entt::registry registry; registry.on_destroy().connect<&listener::sort>(); @@ -1926,3 +1906,85 @@ TEST(Registry, RuntimePools) { ASSERT_FALSE(storage.contains(entity)); ASSERT_FALSE(registry.any_of(entity)); } + +TEST(Registry, StorageProxy) { + using namespace entt::literals; + + entt::registry registry; + const auto entity = registry.create(); + auto &storage = registry.storage("int"_hs); + storage.emplace(entity); + + for(auto [id, pool]: registry.storage()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_TRUE(pool.contains(entity)); + ASSERT_EQ(std::addressof(storage), std::addressof(pool)); + ASSERT_EQ(id, "int"_hs); + } + + for(auto &&curr: std::as_const(registry).storage()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_TRUE(curr.second.contains(entity)); + ASSERT_EQ(std::addressof(storage), std::addressof(curr.second)); + ASSERT_EQ(curr.first, "int"_hs); + } +} + +TEST(Registry, StorageProxyIterator) { + entt::registry registry; + const auto entity = registry.create(); + registry.emplace(entity); + + auto test = [entity](auto iterable) { + auto end{iterable.begin()}; + decltype(end) begin{}; + begin = iterable.end(); + std::swap(begin, end); + + ASSERT_EQ(begin, iterable.cbegin()); + ASSERT_EQ(end, iterable.cend()); + ASSERT_NE(begin, end); + + ASSERT_EQ(begin++, iterable.begin()); + ASSERT_EQ(begin--, iterable.end()); + + ASSERT_EQ(begin + 1, iterable.end()); + ASSERT_EQ(end - 1, iterable.begin()); + + ASSERT_EQ(++begin, iterable.end()); + ASSERT_EQ(--begin, iterable.begin()); + + ASSERT_EQ(begin += 1, iterable.end()); + ASSERT_EQ(begin -= 1, iterable.begin()); + + ASSERT_EQ(begin + (end - begin), iterable.end()); + ASSERT_EQ(begin - (begin - end), iterable.end()); + + ASSERT_EQ(end - (end - begin), iterable.begin()); + ASSERT_EQ(end + (begin - end), iterable.begin()); + + ASSERT_EQ(begin[0u].first, iterable.begin()->first); + ASSERT_EQ(std::addressof(begin[0u].second), std::addressof((*iterable.begin()).second)); + + ASSERT_LT(begin, end); + ASSERT_LE(begin, iterable.begin()); + + ASSERT_GT(end, begin); + ASSERT_GE(end, iterable.end()); + + ASSERT_EQ(begin[0u].first, entt::type_id().hash()); + ASSERT_TRUE(begin[0u].second.contains(entity)); + }; + + test(registry.storage()); + test(std::as_const(registry).storage()); + + decltype(std::as_const(registry).storage().begin()) cit = registry.storage().begin(); + + ASSERT_EQ(cit, registry.storage().begin()); + ASSERT_NE(cit, std::as_const(registry).storage().end()); +} diff --git a/test/example/entity_copy.cpp b/test/example/entity_copy.cpp index eccb7cb5b..09ca2735c 100644 --- a/test/example/entity_copy.cpp +++ b/test/example/entity_copy.cpp @@ -22,17 +22,17 @@ TEST(Example, EntityCopy) { ASSERT_FALSE((registry.any_of(dst))); ASSERT_FALSE(custom.contains(dst)); - registry.visit([src, dst](auto &&storage) { - // discard chars because why not, this is just an example after all - if(storage.type() != entt::type_id() && storage.contains(src)) { + for(auto [id, storage]: registry.storage()) { + // discard the custom storage because why not, this is just an example after all + if(id != "custom"_hs && storage.contains(src)) { storage.emplace(dst, storage.get(src)); } - }); + } - ASSERT_TRUE((registry.all_of(dst))); - ASSERT_FALSE((registry.any_of(dst))); - ASSERT_TRUE(custom.contains(dst)); + ASSERT_TRUE((registry.all_of(dst))); + ASSERT_FALSE((registry.all_of(dst))); + ASSERT_FALSE(custom.contains(dst)); ASSERT_EQ(registry.get(dst), 42); - ASSERT_EQ(custom.get(dst), 1.); + ASSERT_EQ(registry.get(dst), 'c'); }