diff --git a/TODO b/TODO index 2e0cceb4d..b19ccab8e 100644 --- a/TODO +++ b/TODO @@ -10,7 +10,9 @@ WIP: * HP: write documentation for custom storages and views!! -* use extended get to remove is_eto_eligible checks from views and groups. +* view pack: doc and plain function as an alias for operator| +* reverse iterators, rbegin and rend for the view pack +* use extended get to remove is_eto_eligible checks from views and groups * factory invoke: add support for external member functions as static meta functions * pagination doesn't work nicely across boundaries probably, give it a look. RO operations are fine, adding components maybe not. * make it easier to hook into the type system and describe how to do that to eg auto-generate meta types on first use diff --git a/src/entt/entity/view.hpp b/src/entt/entity/view.hpp index 7deb64eb4..c1ff3977e 100644 --- a/src/entt/entity/view.hpp +++ b/src/entt/entity/view.hpp @@ -349,10 +349,6 @@ public: * The returned iterator points to the first entity of the view. If the view * is empty, the returned iterator will be equal to `end()`. * - * @note - * Iterators stay true to the order imposed to the underlying data - * structures. - * * @return An iterator to the first entity of the view. */ [[nodiscard]] iterator begin() const { @@ -366,10 +362,6 @@ public: * the view. Attempting to dereference the returned iterator results in * undefined behavior. * - * @note - * Iterators stay true to the order imposed to the underlying data - * structures. - * * @return An iterator to the entity following the last entity of the view. */ [[nodiscard]] iterator end() const { @@ -382,10 +374,6 @@ public: * The returned iterator points to the first entity of the reversed view. If * the view is empty, the returned iterator will be equal to `rend()`. * - * @note - * Iterators stay true to the order imposed to the underlying data - * structures. - * * @return An iterator to the first entity of the reversed view. */ [[nodiscard]] reverse_iterator rbegin() const { @@ -400,10 +388,6 @@ public: * the reversed view. Attempting to dereference the returned iterator * results in undefined behavior. * - * @note - * Iterators stay true to the order imposed to the underlying data - * structures. - * * @return An iterator to the entity following the last entity of the * reversed view. */ diff --git a/src/entt/entity/view_pack.hpp b/src/entt/entity/view_pack.hpp new file mode 100644 index 000000000..4436a55aa --- /dev/null +++ b/src/entt/entity/view_pack.hpp @@ -0,0 +1,332 @@ +#ifndef ENTT_ENTITY_VIEW_PACK_HPP +#define ENTT_ENTITY_VIEW_PACK_HPP + + +#include +#include +#include +#include "fwd.hpp" +#include "utility.hpp" + + +namespace entt { + + +/** + * @brief View pack. + * + * The view pack allows users to combine multiple views into a single iterable + * object, while also giving them full control over which view should lead the + * iteration.
+ * Its intended primary use is for custom storage and views, but it can also be + * very convenient in everyday use. + * + * @tparam View Type of the leading view of the pack. + * @tparam Other Types of all other views of the pack. + */ +template +class view_pack { + using common_entity_type = std::common_type_t; + + class view_pack_iterator { + friend class view_pack; + + using underlying_iterator = typename View::iterator; + + view_pack_iterator(underlying_iterator from, underlying_iterator to, const std::tuple &ref) ENTT_NOEXCEPT + : it{from}, + last{to}, + pack{std::get(ref)...} + { + if(it != last && !valid()) { + ++(*this); + } + } + + [[nodiscard]] bool valid() const { + const auto entity = *it; + return (std::get(pack).contains(entity) && ...); + } + + public: + using difference_type = typename std::iterator_traits::difference_type; + using value_type = decltype(*std::declval()); + using pointer = void; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + view_pack_iterator & operator++() ENTT_NOEXCEPT { + while(++it != last && !valid()); + return *this; + } + + view_pack_iterator operator++(int) ENTT_NOEXCEPT { + view_pack_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const { + return *it; + } + + [[nodiscard]] bool operator==(const view_pack_iterator &other) const ENTT_NOEXCEPT { + return other.it == it; + } + + [[nodiscard]] bool operator!=(const view_pack_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + + private: + underlying_iterator it; + const underlying_iterator last; + const std::tuple pack; + }; + + class iterable_view_pack { + friend class view_pack; + + using iterable_view = decltype(std::declval().each()); + + class iterable_view_pack_iterator { + friend class iterable_view_pack; + + using underlying_iterator = typename iterable_view::iterator; + + iterable_view_pack_iterator(underlying_iterator from, underlying_iterator to, const std::tuple &ref) ENTT_NOEXCEPT + : it{from}, + last{to}, + pack{ref} + { + if(it != last && !valid()) { + ++(*this); + } + } + + [[nodiscard]] bool valid() const { + const auto entity = std::get<0>(*it); + return (std::get(pack).contains(entity) && ...); + } + + public: + using difference_type = typename std::iterator_traits::difference_type; + using value_type = decltype(std::tuple_cat(*std::declval(), std::declval().get({})...)); + using pointer = void; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + iterable_view_pack_iterator & operator++() ENTT_NOEXCEPT { + while(++it != last && !valid()); + return *this; + } + + iterable_view_pack_iterator operator++(int) ENTT_NOEXCEPT { + iterable_view_pack_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const { + const auto curr = *it; + return std::tuple_cat(curr, std::get(pack).get(std::get<0>(curr))...); + } + + [[nodiscard]] bool operator==(const iterable_view_pack_iterator &other) const ENTT_NOEXCEPT { + return other.it == it; + } + + [[nodiscard]] bool operator!=(const iterable_view_pack_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + + private: + underlying_iterator it; + const underlying_iterator last; + const std::tuple pack; + }; + + iterable_view_pack(const std::tuple &ref) + : iterable{std::get(ref).each()}, + pack{std::get(ref)...} + {} + + public: + using iterator = iterable_view_pack_iterator; + + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return { iterable.begin(), iterable.end(), pack }; + } + + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return { iterable.end(), iterable.end(), pack }; + } + + private: + iterable_view iterable; + std::tuple pack; + }; + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = common_entity_type; + /*! @brief Input iterator type. */ + using iterator = view_pack_iterator; + + /** + * @brief Constructs a pack from a bunch of views. + * @param view A reference to the leading view for the pack. + * @param other References to the other views to use to construct the pack. + */ + view_pack(const View &view, const Other &... other) + : pack{view, other...} + {} + + /** + * @brief Returns an iterator to the first entity of the pack. + * + * The returned iterator points to the first entity of the pack. If the pack + * is empty, the returned iterator will be equal to `end()`. + * + * @note + * Iterators stay true to the order imposed by the first view of the pack. + * + * @return An iterator to the first entity of the pack. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return { std::get(pack).begin(), std::get(pack).end(), pack }; + } + + /** + * @brief Returns an iterator that is past the last entity of the pack. + * + * The returned iterator points to the entity following the last entity of + * the pack. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @note + * Iterators stay true to the order imposed by the first view of the pack. + * + * @return An iterator to the entity following the last entity of the pack. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return { std::get(pack).end(), std::get(pack).end(), pack }; + } + + /** + * @brief Iterates entities and components and applies the given function + * object to them. + * + * The function object is invoked for each entity. It is provided with the + * entity itself and a set of references to non-empty components. The + * _constness_ of the components is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Type &...); + * void(Type &...); + * @endcode + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + for(auto value: std::get(pack).each()) { + const auto entity = std::get<0>(value); + + if((std::get(pack).contains(entity) && ...)) { + std::apply(func, std::tuple_cat(value, std::get(pack).get(entity)...)); + } + } + } + + /** + * @brief Returns an iterable object to use to _visit_ the pack. + * + * The iterable object returns tuples that contain the current entity and a + * set of references to its non-empty components. The _constness_ of the + * components is as requested. + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @return An iterable object to use to _visit_ the pack. + */ + [[nodiscard]] iterable_view_pack each() const ENTT_NOEXCEPT { + return pack; + } + + /** + * @brief Returns a copy of the requested view from a pack. + * @tparam Type Type of the view to return. + * @return A copy of the requested view from the pack. + */ + template + [[nodiscard]] operator Type() const ENTT_NOEXCEPT { + return std::get(pack); + } + + /** + * @brief Appends a view to a pack. + * @tparam Args View template arguments. + * @param view A reference to a view to append to the pack. + * @return The extended pack. + */ + template + [[nodiscard]] auto operator|(const basic_view &view) const { + return std::make_from_tuple>>(std::tuple_cat(pack, std::make_tuple(view))); + } + + /** + * @brief Appends a pack and therefore all its views to another pack. + * @tparam Pack Types of views of the pack to append. + * @param other A reference to the pack to append. + * @return The extended pack. + */ + template + [[nodiscard]] auto operator|(const view_pack &other) const { + return std::make_from_tuple>(std::tuple_cat(pack, std::make_tuple(static_cast(other)...))); + } + +private: + std::tuple pack; +}; + + +/** + * @brief Combines two views in a pack. + * @tparam Args Template arguments of the first view. + * @tparam Other Template arguments of the second view. + * @param lhs A reference to the first view with which to create the pack. + * @param rhs A reference to the second view with which to create the pack. + * @return A pack that combines the two views in a single iterable object. + */ +template +[[nodiscard]] auto operator|(const basic_view &lhs, const basic_view &rhs) { + return view_pack{lhs, rhs}; +} + + +/** + * @brief Combines a view with a pack. + * @tparam Args View template arguments. + * @tparam Pack Types of views of the pack. + * @param view A reference to the view to combine with the pack. + * @param pack A reference to the pack to combine with the view. + * @return The extended pack. + */ +template +[[nodiscard]] auto operator|(const basic_view &view, const view_pack &pack) { + return view_pack{view} | pack; +} + + +} + + +#endif diff --git a/src/entt/entt.hpp b/src/entt/entt.hpp index 2f7ea9411..93d0ed647 100644 --- a/src/entt/entt.hpp +++ b/src/entt/entt.hpp @@ -21,6 +21,7 @@ #include "entity/storage.hpp" #include "entity/utility.hpp" #include "entity/view.hpp" +#include "entity/view_pack.hpp" #include "locator/locator.hpp" #include "meta/container.hpp" #include "meta/ctx.hpp" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5ff5e24fc..19e454b0b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -174,6 +174,7 @@ SETUP_BASIC_TEST(snapshot entt/entity/snapshot.cpp) SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp) SETUP_BASIC_TEST(storage entt/entity/storage.cpp) SETUP_BASIC_TEST(view entt/entity/view.cpp) +SETUP_BASIC_TEST(view_pack entt/entity/view_pack.cpp) # Test locator diff --git a/test/entt/entity/view_pack.cpp b/test/entt/entity/view_pack.cpp new file mode 100644 index 000000000..ce2ffa2f7 --- /dev/null +++ b/test/entt/entity/view_pack.cpp @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include +#include + +struct empty_type {}; + +TEST(ViewPack, Construction) { + entt::registry registry; + + const auto view1 = registry.view(); + const auto view2 = registry.view(); + const auto view3 = registry.view(); + + static_assert(std::is_same_v, int, const char>>>); + static_assert(std::is_same_v); + + static_assert(std::is_same_v< + decltype(entt::view_pack{view1, view2, view3}), + entt::view_pack< + entt::view, int, const char>, + entt::view, empty_type>, + entt::view, double> + > + >); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); +} + +TEST(ViewPack, ShortestPool) { + entt::registry registry; + entt::entity entities[3]; + + registry.create(std::begin(entities), std::end(entities)); + + registry.insert(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities)); + registry.insert(std::rbegin(entities), std::rend(entities) - 1u); + + const auto tmp = registry.view() | registry.view(); + const auto pack = tmp | registry.view(); + + { + std::size_t next{}; + + for(const auto entt: pack) { + ASSERT_EQ(entt::to_integral(entt), ++next); + ASSERT_TRUE((registry.has(entt))); + } + } + + auto it = pack.begin(); + + ASSERT_EQ(*it++, entities[1u]); + ASSERT_EQ(++it, pack.end()); + ASSERT_TRUE(it == pack.end()); + ASSERT_FALSE(it != pack.end()); + + pack.each([®istry, next = 0u](const auto entt, auto &&cv, auto &&iv) mutable { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entt::to_integral(entt), ++next); + ASSERT_EQ(&cv, registry.try_get(entt)); + ASSERT_EQ(&iv, registry.try_get(entt)); + }); + + auto eit = pack.each().begin(); + + ASSERT_EQ(std::get<0>(*eit++), entities[1u]); + static_assert(std::is_same_v(*eit)), char &>); + static_assert(std::is_same_v(*eit)), const int &>); + ASSERT_EQ(++eit, pack.each().end()); + ASSERT_TRUE(eit == pack.each().end()); + ASSERT_FALSE(eit != pack.each().end()); + + { + std::size_t next{}; + + for(const auto [entt, cv, iv]: pack.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entt::to_integral(entt), ++next); + ASSERT_EQ(&cv, registry.try_get(entt)); + ASSERT_EQ(&iv, registry.try_get(entt)); + } + } +} + +TEST(ViewPack, LongestPool) { + entt::registry registry; + entt::entity entities[3]; + + registry.create(std::begin(entities), std::end(entities)); + + registry.insert(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities)); + registry.insert(std::rbegin(entities), std::rend(entities) - 1u); + + const auto pack = registry.view() | registry.view() | registry.view(); + + { + std::size_t next{2u}; + + for(const auto entt: pack) { + ASSERT_EQ(entt::to_integral(entt), next--); + ASSERT_TRUE((registry.has(entt))); + } + } + + auto it = pack.begin(); + + ASSERT_EQ(*it++, entities[2u]); + ASSERT_EQ(++it, pack.end()); + ASSERT_TRUE(it == pack.end()); + ASSERT_FALSE(it != pack.end()); + + pack.each([®istry, next = 2u](const auto entt, auto &&iv, auto &&cv) mutable { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entt::to_integral(entt), next--); + ASSERT_EQ(&iv, registry.try_get(entt)); + ASSERT_EQ(&cv, registry.try_get(entt)); + }); + + auto eit = pack.each().begin(); + + ASSERT_EQ(std::get<0>(*eit++), entities[2u]); + static_assert(std::is_same_v(*eit)), int &>); + static_assert(std::is_same_v(*eit)), const char &>); + ASSERT_EQ(++eit, pack.each().end()); + ASSERT_TRUE(eit == pack.each().end()); + ASSERT_FALSE(eit != pack.each().end()); + + { + std::size_t next{2u}; + + for(const auto [entt, iv, cv]: pack.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entt::to_integral(entt), next--); + ASSERT_EQ(&iv, registry.try_get(entt)); + ASSERT_EQ(&cv, registry.try_get(entt)); + } + } +}