Просмотр исходного кода

view_pack: added more functionalities

Michele Caini 5 лет назад
Родитель
Сommit
dbbb131424
4 измененных файлов с 380 добавлено и 37 удалено
  1. 0 1
      TODO
  2. 9 9
      docs/md/entity.md
  3. 129 27
      src/entt/entity/view_pack.hpp
  4. 242 0
      test/entt/entity/view_pack.cpp

+ 0 - 1
TODO

@@ -17,7 +17,6 @@
 
 WIP:
 * split basic_view and view_t like sfinae-friendly class
-* add view-like functionalities to the view_pack
 * HP: write documentation for custom storages and views!!
 * view pack: plain function as an alias for operator|, reverse iterators, rbegin and rend
 * pagination doesn't work nicely across boundaries probably, give it a look. RO operations are fine, adding components maybe not.

+ 9 - 9
docs/md/entity.md

@@ -1408,10 +1408,10 @@ registry during iterations to get the types iterated by the view itself.
 
 ### 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.<br/>
-This class returns all and only the entities present in all views. Its intended
+The view pack allows users to combine multiple views into a single _view-like_
+iterable object, while also giving them full control over which view should lead
+the iteration.<br/>
+This object returns all and only the entities present in all views. Its intended
 primary use is for custom storage and views, but it can also be very convenient
 in everyday use.
 
@@ -1425,14 +1425,14 @@ auto pack = view | other;
 ```
 
 The return type is a specialization of the class template `entt::view_pack`.
-This is nothing more than an iterable object that combines two or more views
-into a single instance.<br/>
+This is nothing more than a _view-like_ iterable object that combines two or
+more views into a single instance.<br/>
 The first view used to create a pack will also be the same that will lead the
 iteration.
 
-A view pack offers functionalities similar to those of a view, at least as
-regards the possibilities of iteration. In particular, it only returns entities
-if iterated directly:
+A view pack offers functionalities similar to those of a multi component view,
+especially with regard to the possibilities of iteration. In particular, it only
+returns entities if iterated directly:
 
 ```cpp
 for(auto entt: pack) {

+ 129 - 27
src/entt/entity/view_pack.hpp

@@ -2,6 +2,7 @@
 #define ENTT_ENTITY_VIEW_PACK_HPP
 
 
+#include <iterator>
 #include <tuple>
 #include <type_traits>
 #include <utility>
@@ -28,14 +29,11 @@ namespace entt {
  */
 template<typename View, typename... Other>
 class view_pack {
-    using common_entity_type = std::common_type_t<typename View::entity_type, typename Other::entity_type...>;
-
+    template<typename It>
     class view_pack_iterator {
         friend class view_pack<View, Other...>;
 
-        using underlying_iterator = typename View::iterator;
-
-        view_pack_iterator(underlying_iterator from, underlying_iterator to, const std::tuple<View, Other...> &ref) ENTT_NOEXCEPT
+        view_pack_iterator(It from, It to, const std::tuple<View, Other...> &ref) ENTT_NOEXCEPT
             : it{from},
               last{to},
               pack{std::get<Other>(ref)...}
@@ -51,10 +49,10 @@ class view_pack {
         }
 
     public:
-        using difference_type = typename std::iterator_traits<underlying_iterator>::difference_type;
-        using value_type = decltype(*std::declval<underlying_iterator>());
-        using pointer = void;
-        using reference = value_type;
+        using difference_type = typename std::iterator_traits<It>::difference_type;
+        using value_type = typename std::iterator_traits<It>::value_type;
+        using pointer = typename std::iterator_traits<It>::pointer;
+        using reference = typename std::iterator_traits<It>::reference;
         using iterator_category = std::input_iterator_tag;
 
         view_pack_iterator & operator++() ENTT_NOEXCEPT {
@@ -80,8 +78,8 @@ class view_pack {
         }
 
     private:
-        underlying_iterator it;
-        const underlying_iterator last;
+        It it;
+        const It last;
         const std::tuple<Other...> pack;
     };
 
@@ -90,12 +88,11 @@ class view_pack {
 
         using iterable_view = decltype(std::declval<View>().each());
 
+        template<typename It>
         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<Other...> &ref) ENTT_NOEXCEPT
+            iterable_view_pack_iterator(It from, It to, const std::tuple<Other...> &ref) ENTT_NOEXCEPT
                 : it{from},
                   last{to},
                   pack{ref}
@@ -111,8 +108,8 @@ class view_pack {
             }
 
         public:
-            using difference_type = typename std::iterator_traits<underlying_iterator>::difference_type;
-            using value_type = decltype(std::tuple_cat(*std::declval<underlying_iterator>(), std::declval<Other>().get({})...));
+            using difference_type = typename std::iterator_traits<It>::difference_type;
+            using value_type = decltype(std::tuple_cat(*std::declval<It>(), std::declval<Other>().get({})...));
             using pointer = void;
             using reference = value_type;
             using iterator_category = std::input_iterator_tag;
@@ -141,8 +138,8 @@ class view_pack {
             }
 
         private:
-            underlying_iterator it;
-            const underlying_iterator last;
+            It it;
+            const It last;
             const std::tuple<Other...> pack;
         };
 
@@ -152,7 +149,8 @@ class view_pack {
         {}
 
     public:
-        using iterator = iterable_view_pack_iterator;
+        using iterator = iterable_view_pack_iterator<typename iterable_view::iterator>;
+        using reverse_iterator = iterable_view_pack_iterator<typename iterable_view::reverse_iterator>;
 
         [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
             return { iterable.begin(), iterable.end(), pack };
@@ -162,6 +160,14 @@ class view_pack {
             return { iterable.end(), iterable.end(), pack };
         }
 
+        [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
+            return { iterable.rbegin(), iterable.rend(), pack };
+        }
+
+        [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
+            return { iterable.rend(), iterable.rend(), pack };
+        }
+
     private:
         iterable_view iterable;
         std::tuple<Other...> pack;
@@ -169,9 +175,13 @@ class view_pack {
 
 public:
     /*! @brief Underlying entity identifier. */
-    using entity_type = common_entity_type;
+    using entity_type = std::common_type_t<typename View::entity_type, typename Other::entity_type...>;
+    /*! @brief Underlying entity identifier. */
+    using size_type = std::common_type_t<typename View::size_type, typename Other::size_type...>;
     /*! @brief Input iterator type. */
-    using iterator = view_pack_iterator;
+    using iterator = view_pack_iterator<typename View::iterator>;
+    /*! @brief Reversed iterator type. */
+    using reverse_iterator = view_pack_iterator<typename View::reverse_iterator>;
 
     /**
      * @brief Constructs a pack from a bunch of views.
@@ -188,9 +198,6 @@ public:
      * 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 {
@@ -204,15 +211,110 @@ public:
      * 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<View>(pack).end(), std::get<View>(pack).end(), pack };
     }
 
+    /**
+     * @brief Returns an iterator to the first entity of the pack.
+     *
+     * The returned iterator points to the first entity of the reversed pack. If
+     * the pack is empty, the returned iterator will be equal to `rend()`.
+     *
+     * @return An iterator to the first entity of the pack.
+     */
+    [[nodiscard]] reverse_iterator rbegin() const {
+        return { std::get<View>(pack).rbegin(), std::get<View>(pack).rend(), pack };
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last entity of the reversed
+     * pack.
+     *
+     * The returned iterator points to the entity following the last entity of
+     * the reversed pack. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @return An iterator to the entity following the last entity of the
+     * reversed pack.
+     */
+    [[nodiscard]] reverse_iterator rend() const {
+        return { std::get<View>(pack).rend(), std::get<View>(pack).rend(), pack };
+    }
+
+    /**
+     * @brief Returns the first entity of the pack, if any.
+     * @return The first entity of the pack if one exists, the null entity
+     * otherwise.
+     */
+    [[nodiscard]] entity_type front() const {
+        const auto it = begin();
+        return it != end() ? *it : null;
+    }
+
+    /**
+     * @brief Returns the last entity of the pack, if any.
+     * @return The last entity of the pack if one exists, the null entity
+     * otherwise.
+     */
+    [[nodiscard]] entity_type back() const {
+        const auto it = rbegin();
+        return it != rend() ? *it : null;
+    }
+
+    /**
+     * @brief Finds an entity.
+     * @param entt A valid entity identifier.
+     * @return An iterator to the given entity if it's found, past the end
+     * iterator otherwise.
+     */
+    [[nodiscard]] iterator find(const entity_type entt) const {
+        iterator it{std::get<View>(pack).find(entt), std::get<View>(pack).end(), pack};
+        return (it != end() && *it == entt) ? it : end();
+    }
+
+    /**
+     * @brief Checks if a pack contains an entity.
+     * @param entt A valid entity identifier.
+     * @return True if the pack contains the given entity, false otherwise.
+     */
+    [[nodiscard]] bool contains(const entity_type entt) const {
+        return std::apply([entt](auto &&... view) { return (view.contains(entt) && ...); }, pack);
+    }
+
+    /**
+     * @brief Returns the components assigned to the given entity.
+     *
+     * Prefer this function instead of `registry::get` during iterations. It has
+     * far better performance than its counterpart.
+     *
+     * @warning
+     * Attempting to use an invalid component type results in a compilation
+     * error. Attempting to use an entity that doesn't belong to the pack
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * pack doesn't contain the given entity.
+     *
+     * @tparam Comp Types of components to get.
+     * @param entt A valid entity identifier.
+     * @return The components assigned to the entity.
+     */
+    template<typename... Comp>
+    [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const {
+        ENTT_ASSERT(contains(entt));
+        auto component = std::apply([entt](auto &&... view) { return std::tuple_cat(view.get(entt)...); }, pack);
+
+        if constexpr(sizeof...(Comp) == 0) {
+            return component;
+        } else if constexpr(sizeof...(Comp) == 1) {
+            return (std::get<std::add_lvalue_reference_t<Comp>>(component), ...);
+        } else {
+            return std::forward_as_tuple(std::get<std::add_lvalue_reference_t<Comp>>(component)...);
+        }
+    }
+
     /**
      * @brief Iterates entities and components and applies the given function
      * object to them.

+ 242 - 0
test/entt/entity/view_pack.cpp

@@ -32,6 +32,248 @@ TEST(ViewPack, Construction) {
     static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(view1 | view2 | entt::view_pack{view3})>);
 }
 
+TEST(ViewPack, Functionalities) {
+    entt::registry registry;
+    const auto pack = registry.view<int>() | registry.view<char>();
+    const auto cpack = registry.view<const int>() | registry.view<const char>();
+
+    const auto e0 = registry.create();
+    registry.emplace<char>(e0, '1');
+
+    const auto e1 = registry.create();
+    registry.emplace<int>(e1, 42);
+    registry.emplace<char>(e1, '2');
+
+    ASSERT_EQ(*pack.begin(), e1);
+    ASSERT_EQ(*pack.rbegin(), e1);
+    ASSERT_EQ(++pack.begin(), (pack.end()));
+    ASSERT_EQ(++pack.rbegin(), (pack.rend()));
+
+    ASSERT_NO_THROW((pack.begin()++));
+    ASSERT_NO_THROW((++cpack.begin()));
+    ASSERT_NO_THROW(pack.rbegin()++);
+    ASSERT_NO_THROW(++cpack.rbegin());
+
+    ASSERT_NE(pack.begin(), pack.end());
+    ASSERT_NE(cpack.begin(), cpack.end());
+    ASSERT_NE(pack.rbegin(), pack.rend());
+    ASSERT_NE(cpack.rbegin(), cpack.rend());
+
+    for(auto entity: pack) {
+        ASSERT_EQ(std::get<0>(cpack.get<const int, const char>(entity)), 42);
+        ASSERT_EQ(std::get<1>(pack.get<int, char>(entity)), '2');
+        ASSERT_EQ(cpack.get<const char>(entity), '2');
+    }
+}
+
+TEST(ViewPack, Iterator) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    registry.emplace<int>(entity);
+    registry.emplace<char>(entity);
+
+    const auto pack = registry.view<int>() | registry.view<char>();
+    using iterator = typename decltype(pack)::iterator;
+
+    ASSERT_NE(pack.begin(), pack.end());
+    ASSERT_EQ(pack.begin()++, pack.begin());
+    ASSERT_EQ(++pack.begin(), pack.end());
+    ASSERT_EQ(*pack.begin(), entity);
+}
+
+TEST(ViewPack, ReverseIterator) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    registry.emplace<int>(entity);
+    registry.emplace<char>(entity);
+
+    const auto pack = registry.view<int>() | registry.view<char>();
+    using iterator = typename decltype(pack)::reverse_iterator;
+
+    ASSERT_NE(pack.rbegin(), pack.rend());
+    ASSERT_EQ(pack.rbegin()++, pack.rbegin());
+    ASSERT_EQ(++pack.rbegin(), pack.rend());
+    ASSERT_EQ(*pack.rbegin(), entity);
+}
+
+TEST(ViewPack, Contains) {
+    entt::registry registry;
+
+    const auto e0 = registry.create();
+    registry.emplace<int>(e0);
+    registry.emplace<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.emplace<int>(e1);
+    registry.emplace<char>(e1);
+
+    registry.destroy(e0);
+
+    const auto pack = registry.view<int>() | registry.view<char>();
+
+    ASSERT_FALSE(pack.contains(e0));
+    ASSERT_TRUE(pack.contains(e1));
+}
+
+TEST(ViewPack, Each) {
+    entt::registry registry;
+
+    const auto e0 = registry.create();
+    registry.emplace<int>(e0, 0);
+    registry.emplace<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.emplace<int>(e1, 1);
+    registry.emplace<char>(e1);
+
+    auto pack = registry.view<int>() | registry.view<char>();
+    auto cpack = registry.view<const int>() | registry.view<const char>();
+    std::size_t cnt = 0;
+
+    for(auto first = cpack.each().rbegin(), last = cpack.each().rend(); first != last; ++first) {
+        static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &, const char &>>);
+        ASSERT_EQ(std::get<1>(*first), cnt++);
+    }
+
+    pack.each([&cnt](auto, int &, char &) { ++cnt; });
+    pack.each([&cnt](int &, char &) { ++cnt; });
+
+    ASSERT_EQ(cnt, std::size_t{6});
+
+    cpack.each([&cnt](const int &, const char &) { --cnt; });
+    cpack.each([&cnt](auto, const int &, const char &) { --cnt; });
+
+    for(auto &&[entt, iv, cv]: pack.each()) {
+        static_assert(std::is_same_v<decltype(entt), entt::entity>);
+        static_assert(std::is_same_v<decltype(iv), int &>);
+        static_assert(std::is_same_v<decltype(cv), char &>);
+        ASSERT_EQ(iv, --cnt);
+    }
+
+    ASSERT_EQ(cnt, std::size_t{0});
+}
+
+TEST(ViewPack, EachWithHoles) {
+    entt::registry registry;
+
+    const auto e0 = registry.create();
+    const auto e1 = registry.create();
+    const auto e2 = registry.create();
+
+    registry.emplace<char>(e0, '0');
+    registry.emplace<char>(e1, '1');
+
+    registry.emplace<int>(e0, 0);
+    registry.emplace<int>(e2, 2);
+
+    auto pack = registry.view<char>() | registry.view<int>();
+
+    pack.each([e0](auto entity, const char &c, const int &i) {
+        ASSERT_EQ(entity, e0);
+        ASSERT_EQ(c, '0');
+        ASSERT_EQ(i, 0);
+    });
+
+    for(auto &&curr: pack.each()) {
+        ASSERT_EQ(std::get<0>(curr), e0);
+        ASSERT_EQ(std::get<1>(curr), '0');
+        ASSERT_EQ(std::get<2>(curr), 0);
+    }
+}
+
+TEST(ViewPack, ConstNonConstAndAllInBetween) {
+    entt::registry registry;
+    auto pack = registry.view<int, empty_type>() | registry.view<const char>();
+
+    const auto entity = registry.create();
+    registry.emplace<int>(entity, 0);
+    registry.emplace<empty_type>(entity);
+    registry.emplace<char>(entity, 'c');
+
+    static_assert(std::is_same_v<decltype(pack.get<int>({})), int &>);
+    static_assert(std::is_same_v<decltype(pack.get<const char>({})), const char &>);
+    static_assert(std::is_same_v<decltype(pack.get<int, const char>({})), std::tuple<int &, const char &>>);
+    static_assert(std::is_same_v<decltype(pack.get({})), std::tuple<int &, const char &>>);
+
+    pack.each([](auto &&i, auto &&c) {
+        static_assert(std::is_same_v<decltype(i), int &>);
+        static_assert(std::is_same_v<decltype(c), const char &>);
+    });
+
+    for(auto &&[entt, iv, cv]: pack.each()) {
+        static_assert(std::is_same_v<decltype(entt), entt::entity>);
+        static_assert(std::is_same_v<decltype(iv), int &>);
+        static_assert(std::is_same_v<decltype(cv), const char &>);
+    }
+}
+
+TEST(ViewPack, Find) {
+    entt::registry registry;
+    auto pack = registry.view<int>() | registry.view<const char>();
+
+    const auto e0 = registry.create();
+    registry.emplace<int>(e0);
+    registry.emplace<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.emplace<int>(e1);
+    registry.emplace<char>(e1);
+
+    const auto e2 = registry.create();
+    registry.emplace<int>(e2);
+    registry.emplace<char>(e2);
+
+    const auto e3 = registry.create();
+    registry.emplace<int>(e3);
+    registry.emplace<char>(e3);
+
+    registry.remove<int>(e1);
+
+    ASSERT_NE(pack.find(e0), pack.end());
+    ASSERT_EQ(pack.find(e1), pack.end());
+    ASSERT_NE(pack.find(e2), pack.end());
+    ASSERT_NE(pack.find(e3), pack.end());
+
+    auto it = pack.find(e2);
+
+    ASSERT_EQ(*it, e2);
+    ASSERT_EQ(*(++it), e3);
+    ASSERT_EQ(*(++it), e0);
+    ASSERT_EQ(++it, pack.end());
+    ASSERT_EQ(++pack.find(e0), pack.end());
+
+    const auto e4 = registry.create();
+    registry.destroy(e4);
+    const auto e5 = registry.create();
+    registry.emplace<int>(e5);
+    registry.emplace<char>(e5);
+
+    ASSERT_NE(pack.find(e5), pack.end());
+    ASSERT_EQ(pack.find(e4), pack.end());
+}
+
+TEST(ViewPack, FrontBack) {
+    entt::registry registry;
+    auto pack = registry.view<const int>() | registry.view<const char>();
+
+    ASSERT_EQ(pack.front(), static_cast<entt::entity>(entt::null));
+    ASSERT_EQ(pack.back(), static_cast<entt::entity>(entt::null));
+
+    const auto e0 = registry.create();
+    registry.emplace<int>(e0);
+    registry.emplace<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.emplace<int>(e1);
+    registry.emplace<char>(e1);
+
+    const auto entity = registry.create();
+    registry.emplace<char>(entity);
+
+    ASSERT_EQ(pack.front(), e1);
+    ASSERT_EQ(pack.back(), e0);
+}
+
 TEST(ViewPack, ShortestPool) {
     entt::registry registry;
     entt::entity entities[4];