Przeglądaj źródła

added [view|group]::less

Michele Caini 6 lat temu
rodzic
commit
161b5530b9

+ 4 - 1
docs/md/entity.md

@@ -1016,7 +1016,10 @@ registry.view<position, velocity>().each([](auto entity, auto &pos, auto &vel) {
 The `each` member function is highly optimized. Unless users want to iterate
 only entities or get only some of the components, this should be the preferred
 approach. Note that the entity can also be excluded from the parameter list if
-not required, but this won't improve performance for multi component views.
+not required, but this won't improve performance for multi component views.<br/>
+There exists also an alternative version of `each` named `less` that works
+exactly as its counterpart but for the fact that it doesn't return empty
+components to the caller.
 
 As a side note, when using a single component view, the most common error is to
 invoke `get` with the type of the component as a template parameter. This is

+ 8 - 0
src/entt/core/type_traits.hpp

@@ -25,6 +25,14 @@ template<typename...>
 struct type_list_cat;
 
 
+/*! @brief Concatenates multiple type lists. */
+template<>
+struct type_list_cat<> {
+    /*! @brief A type list composed by the types of all the type lists. */
+    using type = type_list<>;
+};
+
+
 /**
  * @brief Concatenates multiple type lists.
  * @tparam Type Types provided by the first type list.

+ 86 - 23
src/entt/entity/group.hpp

@@ -94,6 +94,17 @@ class basic_group<Entity, get_t<Get...>> {
           pools{get...}
     {}
 
+    template<typename Func, typename... Weak>
+    inline void traverse(Func func, type_list<Weak...>) const {
+        for(const auto entt: *handler) {
+            if constexpr(std::is_invocable_v<Func, decltype(get<Weak>({}))...>) {
+                func(std::get<pool_type<Weak> *>(pools)->get(entt)...);
+            } else {
+                func(entt, std::get<pool_type<Weak> *>(pools)->get(entt)...);
+            }
+        };
+    }
+
 public:
     /*! @brief Underlying entity identifier. */
     using entity_type = typename sparse_set<Entity>::entity_type;
@@ -326,13 +337,33 @@ public:
      */
     template<typename Func>
     inline void each(Func func) const {
-        for(const auto entt: *handler) {
-            if constexpr(std::is_invocable_v<Func, decltype(get<Get>({}))...>) {
-                func(std::get<pool_type<Get> *>(pools)->get(entt)...);
-            } else {
-                func(entt, std::get<pool_type<Get> *>(pools)->get(entt)...);
-            }
-        };
+        traverse(std::move(func), type_list<Get...>{});
+    }
+
+    /**
+     * @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.<br/>
+     * The signature of the function must be equivalent to one of the following
+     * forms:
+     *
+     * @code{.cpp}
+     * void(const entity_type, Type &...);
+     * void(Type &...);
+     * @endcode
+     *
+     * @sa each
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    inline void less(Func func) const {
+        using non_empty_get = type_list_cat_t<std::conditional_t<std::is_empty_v<Get>, type_list<>, type_list<Get>>...>;
+        traverse(std::move(func), non_empty_get{});
     }
 
     /**
@@ -450,6 +481,26 @@ class basic_group<Entity, get_t<Get...>, Owned...> {
         std::get<pool_type<Component> *>(pools)->swap(lhs, rhs);
     }
 
+    template<typename Func, typename... Strong, typename... Weak>
+    inline void traverse(Func func, type_list<Strong...>, type_list<Weak...>) const {
+        auto raw = std::make_tuple((std::get<pool_type<Strong> *>(pools)->end() - *length)...);
+        [[maybe_unused]] auto data = std::get<0>(pools)->sparse_set<entity_type>::end() - *length;
+
+        for(auto next = *length; next; --next) {
+            if constexpr(std::is_invocable_v<Func, decltype(get<Strong>({}))..., decltype(get<Weak>({}))...>) {
+                if constexpr(sizeof...(Weak) == 0) {
+                    func(*(std::get<component_iterator_type<Strong>>(raw)++)...);
+                } else {
+                    const auto entt = *(data++);
+                    func(*(std::get<component_iterator_type<Strong>>(raw)++)..., std::get<pool_type<Weak> *>(pools)->get(entt)...);
+                }
+            } else {
+                const auto entt = *(data++);
+                func(entt, *(std::get<component_iterator_type<Strong>>(raw)++)..., std::get<pool_type<Weak> *>(pools)->get(entt)...);
+            }
+        }
+    }
+
 public:
     /*! @brief Underlying entity identifier. */
     using entity_type = typename sparse_set<Entity>::entity_type;
@@ -674,22 +725,34 @@ public:
      */
     template<typename Func>
     inline void each(Func func) const {
-        auto raw = std::make_tuple((std::get<pool_type<Owned> *>(pools)->end() - *length)...);
-        [[maybe_unused]] auto data = std::get<0>(pools)->sparse_set<entity_type>::end() - *length;
+        traverse(std::move(func), type_list<Owned...>{}, type_list<Get...>{});
+    }
 
-        for(auto next = *length; next; --next) {
-            if constexpr(std::is_invocable_v<Func, decltype(get<Owned>({}))..., decltype(get<Get>({}))...>) {
-                if constexpr(sizeof...(Get) == 0) {
-                    func(*(std::get<component_iterator_type<Owned>>(raw)++)...);
-                } else {
-                    const auto entt = *(data++);
-                    func(*(std::get<component_iterator_type<Owned>>(raw)++)..., std::get<pool_type<Get> *>(pools)->get(entt)...);
-                }
-            } else {
-                const auto entt = *(data++);
-                func(entt, *(std::get<component_iterator_type<Owned>>(raw)++)..., std::get<pool_type<Get> *>(pools)->get(entt)...);
-            }
-        }
+    /**
+     * @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.<br/>
+     * The signature of the function must be equivalent to one of the following
+     * forms:
+     *
+     * @code{.cpp}
+     * void(const entity_type, Type &...);
+     * void(Type &...);
+     * @endcode
+     *
+     * @sa each
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    inline void less(Func func) const {
+        using non_empty_owned = type_list_cat_t<std::conditional_t<std::is_empty_v<Owned>, type_list<>, type_list<Owned>>...>;
+        using non_empty_get = type_list_cat_t<std::conditional_t<std::is_empty_v<Get>, type_list<>, type_list<Get>>...>;
+        traverse(std::move(func), non_empty_owned{}, non_empty_get{});
     }
 
     /**
@@ -753,7 +816,7 @@ public:
             }, std::forward<Args>(args)...);
         }
 
-        for(size_type pos = 0, last = copy.size(); pos < last; ++pos) {
+        for(size_type pos{}, last = copy.size(); pos < last; ++pos) {
             auto curr = pos;
             auto next = copy[curr];
 

+ 2 - 2
src/entt/entity/registry.hpp

@@ -1213,8 +1213,8 @@ public:
         ENTT_ASSERT(valid(entity));
         bool orphan = true;
 
-        for(std::size_t i = {}, last = pools.size(); i < last && orphan; ++i) {
-            const auto &pdata = pools[i];
+        for(std::size_t pos{}, last = pools.size(); pos < last && orphan; ++pos) {
+            const auto &pdata = pools[pos];
             orphan = !(pdata.pool && pdata.pool->has(entity));
         }
 

+ 2 - 2
src/entt/entity/snapshot.hpp

@@ -140,8 +140,8 @@ public:
 
             archive(static_cast<Entity>(sz));
 
-            for(std::remove_const_t<decltype(sz)> i{}; i < sz; ++i) {
-                const auto entt = entities[i];
+            for(std::remove_const_t<decltype(sz)> pos{}; pos < sz; ++pos) {
+                const auto entt = entities[pos];
 
                 if constexpr(std::is_empty_v<Component...>) {
                     archive(entt);

+ 5 - 5
src/entt/entity/sparse_set.hpp

@@ -188,11 +188,11 @@ public:
         : reverse{},
           direct{other.direct}
     {
-        for(size_type i = {}, last = other.reverse.size(); i < last; ++i) {
-            if(other.reverse[i].first) {
-                assure(i);
-                std::copy_n(other.reverse[i].first.get(), entt_per_page, reverse[i].first.get());
-                reverse[i].second = other.reverse[i].second;
+        for(size_type pos{}, last = other.reverse.size(); pos < last; ++pos) {
+            if(other.reverse[pos].first) {
+                assure(pos);
+                std::copy_n(other.reverse[pos].first.get(), entt_per_page, reverse[pos].first.get());
+                reverse[pos].second = other.reverse[pos].second;
             }
         }
     }

+ 1 - 1
src/entt/entity/storage.hpp

@@ -434,7 +434,7 @@ public:
             }, std::forward<Args>(args)...);
         }
 
-        for(size_type pos = 0, last = copy.size(); pos < last; ++pos) {
+        for(size_type pos{}, last = copy.size(); pos < last; ++pos) {
             auto curr = pos;
             auto next = copy[curr];
 

+ 128 - 13
src/entt/entity/view.hpp

@@ -170,22 +170,34 @@ class basic_view {
         }
     }
 
-    template<typename Comp, typename... Other, typename Func>
-    void each(type_list<Other...>, Func func) const {
+    template<typename Comp, typename Func, typename... Other, typename... Type>
+    void traverse(Func func, type_list<Other...>, type_list<Type...>) const {
         const auto end = std::get<pool_type<Comp> *>(pools)->sparse_set<Entity>::end();
         auto begin = std::get<pool_type<Comp> *>(pools)->sparse_set<Entity>::begin();
 
-        std::for_each(begin, end, [raw = std::get<pool_type<Comp> *>(pools)->begin(), &func, this](const auto entity) mutable {
-            auto curr = raw++;
+        if constexpr(std::disjunction_v<std::is_same<Comp, Type>...>) {
+            std::for_each(begin, end, [raw = std::get<pool_type<Comp> *>(pools)->begin(), &func, this](const auto entity) mutable {
+                auto curr = raw++;
 
-            if((std::get<pool_type<Other> *>(pools)->has(entity) && ...)) {
-                if constexpr(std::is_invocable_v<Func, decltype(get<Component>({}))...>) {
-                    func(get<Comp, Component>(curr, std::get<pool_type<Component> *>(pools), entity)...);
-                } else {
-                    func(entity, get<Comp, Component>(curr, std::get<pool_type<Component> *>(pools), entity)...);
+                if((std::get<pool_type<Other> *>(pools)->has(entity) && ...)) {
+                    if constexpr(std::is_invocable_v<Func, decltype(get<Type>({}))...>) {
+                        func(get<Comp, Type>(curr, std::get<pool_type<Type> *>(pools), entity)...);
+                    } else {
+                        func(entity, get<Comp, Type>(curr, std::get<pool_type<Type> *>(pools), entity)...);
+                    }
                 }
-            }
-        });
+            });
+        } else {
+            std::for_each(begin, end, [&func, this](const auto entity) mutable {
+                if((std::get<pool_type<Other> *>(pools)->has(entity) && ...)) {
+                    if constexpr(std::is_invocable_v<Func, decltype(get<Type>({}))...>) {
+                        func(std::get<pool_type<Type> *>(pools)->get(entity)...);
+                    } else {
+                        func(entity, std::get<pool_type<Type> *>(pools)->get(entity)...);
+                    }
+                }
+            });
+        }
     }
 
 public:
@@ -421,7 +433,67 @@ public:
     template<typename Comp, typename Func>
     inline void each(Func func) const {
         using other_type = type_list_cat_t<std::conditional_t<std::is_same_v<Comp, Component>, type_list<>, type_list<Component>>...>;
-        each<Comp>(other_type{}, std::move(func));
+        traverse<Comp>(std::move(func), other_type{}, type_list<Component...>{});
+    }
+
+    /**
+     * @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.<br/>
+     * The signature of the function must be equivalent to one of the following
+     * forms:
+     *
+     * @code{.cpp}
+     * void(const entity_type, Type &...);
+     * void(Type &...);
+     * @endcode
+     *
+     * @sa each
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    inline void less(Func func) const {
+        const auto *view = candidate();
+        ((std::get<pool_type<Component> *>(pools) == view ? less<Component>(std::move(func)) : void()), ...);
+    }
+
+    /**
+     * @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.<br/>
+     * The signature of the function must be equivalent to one of the following
+     * forms:
+     *
+     * @code{.cpp}
+     * void(const entity_type, Type &...);
+     * void(Type &...);
+     * @endcode
+     *
+     * The pool of the suggested component is used to lead the iterations. The
+     * returned entities will therefore respect the order of the pool associated
+     * with that type.<br/>
+     * It is no longer guaranteed that the performance is the best possible, but
+     * there will be greater control over the order of iteration.
+     *
+     * @sa each
+     *
+     * @tparam Comp Type of component to use to enforce the iteration order.
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Comp, typename Func>
+    inline void less(Func func) const {
+        using other_type = type_list_cat_t<std::conditional_t<std::is_same_v<Comp, Component>, type_list<>, type_list<Component>>...>;
+        using non_empty_type = type_list_cat_t<std::conditional_t<std::is_empty_v<Component>, type_list<>, type_list<Component>>...>;
+        traverse<Comp>(std::move(func), other_type{}, non_empty_type{});
     }
 
 private:
@@ -641,7 +713,7 @@ public:
      * @param func A valid function object.
      */
     template<typename Func>
-    void each(Func func) const {
+    inline void each(Func func) const {
         if constexpr(std::is_invocable_v<Func, decltype(get({}))>) {
             std::for_each(pool->begin(), pool->end(), std::move(func));
         } else {
@@ -651,6 +723,49 @@ public:
         }
     }
 
+    /**
+     * @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 reference to its component if it's a non-empty one.
+     * The _constness_ of the component is as requested.<br/>
+     * The signature of the function must be equivalent to one of the following
+     * forms in case the component isn't an empty one:
+     *
+     * @code{.cpp}
+     * void(const entity_type, Component &);
+     * void(Component &);
+     * @endcode
+     *
+     * In case the component is an empty one instead, the following forms are
+     * accepted:
+     *
+     * @code{.cpp}
+     * void(const entity_type);
+     * void();
+     * @endcode
+     *
+     * @sa each
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    inline void less(Func func) const {
+        if constexpr(std::is_empty_v<Component>) {
+            if constexpr(std::is_invocable_v<Func>) {
+                for(auto pos = pool->size(); pos; --pos) {
+                    func();
+                }
+            } else {
+                std::for_each(pool->sparse_set<Entity>::begin(), pool->sparse_set<Entity>::end(), std::move(func));
+            }
+        } else {
+            each(std::move(func));
+        }
+    }
+
 private:
     pool_type *pool;
 };

+ 64 - 17
test/entt/entity/group.cpp

@@ -2,6 +2,7 @@
 #include <iterator>
 #include <algorithm>
 #include <gtest/gtest.h>
+#include <entt/entity/helper.hpp>
 #include <entt/entity/registry.hpp>
 #include <entt/entity/group.hpp>
 
@@ -10,8 +11,8 @@ struct boxed_int { int value; };
 
 TEST(NonOwningGroup, Functionalities) {
     entt::registry registry;
-    auto group = registry.group<>(entt::get<int, char>);
-    auto cgroup = std::as_const(registry).group<>(entt::get<const int, const char>);
+    auto group = registry.group(entt::get<int, char>);
+    auto cgroup = std::as_const(registry).group(entt::get<const int, const char>);
 
     ASSERT_TRUE(group.empty());
     ASSERT_TRUE(group.empty<int>());
@@ -84,8 +85,8 @@ TEST(NonOwningGroup, Functionalities) {
 
 TEST(NonOwningGroup, ElementAccess) {
     entt::registry registry;
-    auto group = registry.group<>(entt::get<int, char>);
-    auto cgroup = std::as_const(registry).group<>(entt::get<const int, const char>);
+    auto group = registry.group(entt::get<int, char>);
+    auto cgroup = std::as_const(registry).group(entt::get<const int, const char>);
 
     const auto e0 = registry.create();
     registry.assign<int>(e0);
@@ -103,7 +104,7 @@ TEST(NonOwningGroup, ElementAccess) {
 
 TEST(NonOwningGroup, Contains) {
     entt::registry registry;
-    auto group = registry.group<>(entt::get<int, char>);
+    auto group = registry.group(entt::get<int, char>);
 
     const auto e0 = registry.create();
     registry.assign<int>(e0);
@@ -131,12 +132,12 @@ TEST(NonOwningGroup, Empty) {
     registry.assign<char>(e1);
     registry.assign<float>(e1);
 
-    for(auto entity: registry.group<>(entt::get<char, int, float>)) {
+    for(auto entity: registry.group(entt::get<char, int, float>)) {
         (void)entity;
         FAIL();
     }
 
-    for(auto entity: registry.group<>(entt::get<double, char, int, float>)) {
+    for(auto entity: registry.group(entt::get<double, char, int, float>)) {
         (void)entity;
         FAIL();
     }
@@ -144,7 +145,7 @@ TEST(NonOwningGroup, Empty) {
 
 TEST(NonOwningGroup, Each) {
     entt::registry registry;
-    auto group = registry.group<>(entt::get<int, char>);
+    auto group = registry.group(entt::get<int, char>);
 
     const auto e0 = registry.create();
     registry.assign<int>(e0);
@@ -154,7 +155,7 @@ TEST(NonOwningGroup, Each) {
     registry.assign<int>(e1);
     registry.assign<char>(e1);
 
-    auto cgroup = std::as_const(registry).group<>(entt::get<const int, const char>);
+    auto cgroup = std::as_const(registry).group(entt::get<const int, const char>);
     std::size_t cnt = 0;
 
     group.each([&cnt](auto, int &, char &) { ++cnt; });
@@ -170,7 +171,7 @@ TEST(NonOwningGroup, Each) {
 
 TEST(NonOwningGroup, Sort) {
     entt::registry registry;
-    auto group = registry.group<>(entt::get<const int, unsigned int>);
+    auto group = registry.group(entt::get<const int, unsigned int>);
 
     const auto e0 = registry.create();
     const auto e1 = registry.create();
@@ -203,7 +204,7 @@ TEST(NonOwningGroup, Sort) {
 
 TEST(NonOwningGroup, IndexRebuiltOnDestroy) {
     entt::registry registry;
-    auto group = registry.group<>(entt::get<int, unsigned int>);
+    auto group = registry.group(entt::get<int, unsigned int>);
 
     const auto e0 = registry.create();
     const auto e1 = registry.create();
@@ -231,7 +232,7 @@ TEST(NonOwningGroup, IndexRebuiltOnDestroy) {
 
 TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
     entt::registry registry;
-    auto group = registry.group<>(entt::get<int, const char>);
+    auto group = registry.group(entt::get<int, const char>);
 
     ASSERT_EQ(group.size(), decltype(group.size()){0});
 
@@ -255,7 +256,7 @@ TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
 
 TEST(NonOwningGroup, Find) {
     entt::registry registry;
-    auto group = registry.group<>(entt::get<int, const char>);
+    auto group = registry.group(entt::get<int, const char>);
 
     const auto e0 = registry.create();
     registry.assign<int>(e0);
@@ -308,7 +309,7 @@ TEST(NonOwningGroup, ExcludedComponents) {
     registry.assign<int>(e1, 1);
     registry.assign<char>(e1);
 
-    const auto group = registry.group<>(entt::get<int>, entt::exclude<char>);
+    const auto group = registry.group(entt::get<int>, entt::exclude<char>);
 
     const auto e2 = registry.create();
     registry.assign<int>(e2, 2);
@@ -348,7 +349,7 @@ TEST(NonOwningGroup, ExcludedComponents) {
 
 TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
     entt::registry registry;
-    const auto group = registry.group<>(entt::get<int, empty_type>);
+    const auto group = registry.group(entt::get<int, empty_type>);
 
     const auto e0 = registry.create();
     registry.assign<empty_type>(e0);
@@ -373,8 +374,8 @@ TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
 
 TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) {
     entt::registry registry;
-    const auto group = registry.group<>(entt::get<int>, entt::exclude<char>);
-    const auto cgroup = std::as_const(registry).group<>(entt::get<const int>, entt::exclude<char>);
+    const auto group = registry.group(entt::get<int>, entt::exclude<char>);
+    const auto cgroup = std::as_const(registry).group(entt::get<const int>, entt::exclude<char>);
 
     const auto entity = registry.create();
     registry.assign<int>(entity);
@@ -389,6 +390,29 @@ TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) {
     ASSERT_FALSE(cgroup.empty());
 }
 
+TEST(NonOwningGroup, Less) {
+    entt::registry registry;
+    const auto entity = std::get<0>(registry.create<int, entt::tag<"empty"_hs>>());
+    registry.create<char>();
+
+    registry.group(entt::get<int, char, entt::tag<"empty"_hs>>).less([entity](const auto entt, int, char) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    registry.group(entt::get<int, entt::tag<"empty"_hs>, char>).less([check = true](int, char) mutable {
+        ASSERT_TRUE(check);
+        check = false;
+    });
+
+    registry.group(entt::get<entt::tag<"empty"_hs>, int, char>).less([entity](const auto entt, int, char) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    registry.group(entt::get<int, char, double>).less([entity](const auto entt, int, char, double) {
+        ASSERT_EQ(entity, entt);
+    });
+}
+
 TEST(OwningGroup, Functionalities) {
     entt::registry registry;
     auto group = registry.group<int>(entt::get<char>);
@@ -890,3 +914,26 @@ TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {
     ASSERT_FALSE(group.empty());
     ASSERT_FALSE(cgroup.empty());
 }
+
+TEST(OwningGroup, Less) {
+    entt::registry registry;
+    const auto entity = std::get<0>(registry.create<int, entt::tag<"empty"_hs>>());
+    registry.create<char>();
+
+    registry.group<int>(entt::get<char, entt::tag<"empty"_hs>>).less([entity](const auto entt, int, char) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    registry.group<char>(entt::get<entt::tag<"empty"_hs>, int>).less([check = true](int, char) mutable {
+        ASSERT_TRUE(check);
+        check = false;
+    });
+
+    registry.group<entt::tag<"empty"_hs>>(entt::get<int, char>).less([entity](const auto entt, int, char) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    registry.group<double>(entt::get<int, char>).less([entity](const auto entt, int, char, double) {
+        ASSERT_EQ(entity, entt);
+    });
+}

+ 60 - 9
test/entt/entity/view.cpp

@@ -1,6 +1,7 @@
 #include <utility>
 #include <type_traits>
 #include <gtest/gtest.h>
+#include <entt/entity/helper.hpp>
 #include <entt/entity/registry.hpp>
 #include <entt/entity/view.hpp>
 
@@ -96,11 +97,7 @@ TEST(SingleComponentView, Empty) {
     auto view = registry.view<int>();
 
     ASSERT_EQ(view.size(), entt::registry::size_type{0});
-
-    for(auto entity: view) {
-        (void)entity;
-        FAIL();
-    }
+    ASSERT_EQ(view.begin(), view.end());
 }
 
 TEST(SingleComponentView, Each) {
@@ -193,6 +190,30 @@ TEST(SingleComponentView, Find) {
     ASSERT_EQ(view.find(e4), view.end());
 }
 
+TEST(SingleComponentView, Less) {
+    entt::registry registry;
+    const auto entity = std::get<0>(registry.create<int, entt::tag<"empty"_hs>>());
+    registry.create<char>();
+
+    registry.view<entt::tag<"empty"_hs>>().less([entity](const auto entt) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    registry.view<entt::tag<"empty"_hs>>().less([check = true]() mutable {
+        ASSERT_TRUE(check);
+        check = false;
+    });
+
+    registry.view<int>().less([entity](const auto entt, int) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    registry.view<int>().less([check = true](int) mutable {
+        ASSERT_TRUE(check);
+        check = false;
+    });
+}
+
 TEST(MultipleComponentView, Functionalities) {
     entt::registry registry;
     auto view = registry.view<int, char>();
@@ -302,10 +323,8 @@ TEST(MultipleComponentView, Empty) {
 
     auto view = registry.view<char, int, float>();
 
-    for(auto entity: view) {
-        (void)entity;
-        FAIL();
-    }
+    ASSERT_EQ(view.size(), entt::registry::size_type{1});
+    ASSERT_EQ(view.begin(), view.end());
 }
 
 TEST(MultipleComponentView, Each) {
@@ -453,3 +472,35 @@ TEST(MultipleComponentView, Find) {
     ASSERT_NE(view.find(e5), view.end());
     ASSERT_EQ(view.find(e4), view.end());
 }
+
+TEST(MultiComponentView, Less) {
+    entt::registry registry;
+    const auto entity = std::get<0>(registry.create<int, char, double, entt::tag<"empty"_hs>>());
+    registry.create<int, char>();
+
+    registry.view<int, char, entt::tag<"empty"_hs>>().less([entity](const auto entt, int, char) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    registry.view<int, entt::tag<"empty"_hs>, char>().less([check = true](int, char) mutable {
+        ASSERT_TRUE(check);
+        check = false;
+    });
+
+    registry.view<entt::tag<"empty"_hs>, int, char>().less([entity](const auto entt, int, char) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    registry.view<entt::tag<"empty"_hs>, int, char>().less<entt::tag<"empty"_hs>>([entity](const auto entt, int, char) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    registry.view<int, entt::tag<"empty"_hs>, char>().less<entt::tag<"empty"_hs>>([check = true](int, char) mutable {
+        ASSERT_TRUE(check);
+        check = false;
+    });
+
+    registry.view<int, char, double>().less([entity](const auto entt, int, char, double) {
+        ASSERT_EQ(entity, entt);
+    });
+}