Explorar el Código

review: views and sparse sets

Michele Caini hace 8 años
padre
commit
18451edfe9
Se han modificado 4 ficheros con 403 adiciones y 63 borrados
  1. 92 20
      src/entt/entity/sparse_set.hpp
  2. 254 33
      src/entt/entity/view.hpp
  3. 30 0
      test/entt/entity/registry.cpp
  4. 27 10
      test/entt/entity/view.cpp

+ 92 - 20
src/entt/entity/sparse_set.hpp

@@ -61,11 +61,11 @@ class SparseSet<Entity> {
     struct Iterator final {
         using difference_type = std::size_t;
         using value_type = Entity;
-        using pointer = value_type *;
+        using pointer = const value_type *;
         using reference = value_type;
         using iterator_category = std::input_iterator_tag;
 
-        Iterator(const std::vector<value_type> &direct, std::size_t pos)
+        Iterator(pointer direct, std::size_t pos)
             : direct{direct}, pos{pos}
         {}
 
@@ -100,7 +100,7 @@ class SparseSet<Entity> {
         }
 
     private:
-        const std::vector<value_type> &direct;
+        pointer direct;
         std::size_t pos;
     };
 
@@ -115,6 +115,8 @@ public:
     using size_type = std::size_t;
     /*! @brief Input iterator type. */
     using iterator_type = Iterator;
+    /*! @brief Constant input iterator type. */
+    using const_iterator_type = Iterator;
 
     /*! @brief Default constructor. */
     SparseSet() ENTT_NOEXCEPT = default;
@@ -211,8 +213,24 @@ public:
      *
      * @return An iterator to the first entity of the internal packed array.
      */
-    iterator_type begin() const ENTT_NOEXCEPT {
-        return Iterator{direct, direct.size()};
+    const_iterator_type cbegin() const ENTT_NOEXCEPT {
+        return const_iterator_type{direct.data(), direct.size()};
+    }
+
+    /**
+     * @brief Returns an iterator to the beginning.
+     *
+     * The returned iterator points to the first entity of the internal packed
+     * array. If the sparse set is empty, the returned iterator will be equal to
+     * `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed by a call to `respect`.
+     *
+     * @return An iterator to the first entity of the internal packed array.
+     */
+    inline iterator_type begin() ENTT_NOEXCEPT {
+        return cbegin();
     }
 
     /**
@@ -228,8 +246,25 @@ public:
      * @return An iterator to the element following the last entity of the
      * internal packed array.
      */
-    iterator_type end() const ENTT_NOEXCEPT {
-        return Iterator{direct, 0};
+    const_iterator_type cend() const ENTT_NOEXCEPT {
+        return const_iterator_type{direct.data(), 0};
+    }
+
+    /**
+     * @brief Returns an iterator to the end.
+     *
+     * The returned iterator points to the element following the last entity in
+     * the internal packed array. Attempting to dereference the returned
+     * iterator results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed by a call to `respect`.
+     *
+     * @return An iterator to the element following the last entity of the
+     * internal packed array.
+     */
+    inline iterator_type end() ENTT_NOEXCEPT {
+        return cend();
     }
 
     /**
@@ -374,8 +409,8 @@ public:
      * @param other The sparse sets that imposes the order of the entities.
      */
     void respect(const SparseSet<Entity> &other) ENTT_NOEXCEPT {
-        auto from = other.begin();
-        auto to = other.end();
+        auto from = other.cbegin();
+        auto to = other.cend();
 
         pos_type pos = direct.size() - 1;
 
@@ -432,14 +467,15 @@ template<typename Entity, typename Type>
 class SparseSet<Entity, Type>: public SparseSet<Entity> {
     using underlying_type = SparseSet<Entity>;
 
+    template<bool Const>
     struct Iterator final {
         using difference_type = std::size_t;
-        using value_type = Type;
+        using value_type = std::conditional_t<Const, const Type, Type>;
         using pointer = value_type *;
         using reference = value_type &;
         using iterator_category = std::input_iterator_tag;
 
-        Iterator(std::vector<value_type> &instances, std::size_t pos)
+        Iterator(pointer instances, std::size_t pos)
             : instances{instances}, pos{pos}
         {}
 
@@ -469,16 +505,16 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
             return !(*this == other);
         }
 
-        reference operator*() ENTT_NOEXCEPT {
+        reference operator*() const ENTT_NOEXCEPT {
             return instances[pos-1];
         }
 
-        pointer operator->() ENTT_NOEXCEPT {
-            return &instances.data()[pos-1];
+        pointer operator->() const ENTT_NOEXCEPT {
+            return (instances+pos-1);
         }
 
     private:
-        std::vector<value_type> &instances;
+        pointer instances;
         std::size_t pos;
     };
 
@@ -492,7 +528,9 @@ public:
     /*! @brief Unsigned integer type. */
     using size_type = typename underlying_type::size_type;
     /*! @brief Input iterator type. */
-    using iterator_type = Iterator;
+    using iterator_type = Iterator<false>;
+    /*! @brief Constant input iterator type. */
+    using const_iterator_type = Iterator<true>;
 
     /*! @brief Default constructor. */
     SparseSet() ENTT_NOEXCEPT = default;
@@ -558,6 +596,22 @@ public:
         return instances.data();
     }
 
+    /**
+     * @brief Returns an iterator to the beginning.
+     *
+     * The returned iterator points to the first instance of the given type. If
+     * the sparse set is empty, the returned iterator will be equal to `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed by a call to either `sort`
+     * or `respect`.
+     *
+     * @return An iterator to the first instance of the given type.
+     */
+    const_iterator_type cbegin() const ENTT_NOEXCEPT {
+        return const_iterator_type{instances.data(), instances.size()};
+    }
+
     /**
      * @brief Returns an iterator to the beginning.
      *
@@ -571,7 +625,25 @@ public:
      * @return An iterator to the first instance of the given type.
      */
     iterator_type begin() ENTT_NOEXCEPT {
-        return Iterator{instances, instances.size()};
+        return iterator_type{instances.data(), instances.size()};
+    }
+
+    /**
+     * @brief Returns an iterator to the end.
+     *
+     * The returned iterator points to the element following the last instance
+     * of the given type. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed by a call to either `sort`
+     * or `respect`.
+     *
+     * @return An iterator to the element following the last instance of the
+     * given type.
+     */
+    const_iterator_type cend() const ENTT_NOEXCEPT {
+        return const_iterator_type{instances.data(), 0};
     }
 
     /**
@@ -589,7 +661,7 @@ public:
      * given type.
      */
     iterator_type end() ENTT_NOEXCEPT {
-        return Iterator{instances, 0};
+        return iterator_type{instances.data(), 0};
     }
 
     /**
@@ -772,8 +844,8 @@ public:
      * @param other The sparse sets that imposes the order of the entities.
      */
     void respect(const SparseSet<Entity> &other) ENTT_NOEXCEPT {
-        auto from = other.begin();
-        auto to = other.end();
+        auto from = other.cbegin();
+        auto to = other.cend();
 
         pos_type pos = underlying_type::size() - 1;
         const auto *local = underlying_type::data();

+ 254 - 33
src/entt/entity/view.hpp

@@ -84,6 +84,8 @@ class PersistentView final {
 public:
     /*! @brief Input iterator type. */
     using iterator_type = typename view_type::iterator_type;
+    /*! @brief Constant input iterator type. */
+    using const_iterator_type = typename view_type::const_iterator_type;
     /*! @brief Underlying entity identifier. */
     using entity_type = typename view_type::entity_type;
     /*! @brief Unsigned integer type. */
@@ -97,6 +99,14 @@ public:
         return view.size();
     }
 
+    /**
+     * @brief Checks whether the view is empty.
+     * @return True if the view is empty, false otherwise.
+     */
+    bool empty() const ENTT_NOEXCEPT {
+        return view.empty();
+    }
+
     /**
      * @brief Direct access to the list of entities.
      *
@@ -127,7 +137,25 @@ public:
      *
      * @return An iterator to the first entity that has the given components.
      */
-    iterator_type begin() const ENTT_NOEXCEPT {
+    const_iterator_type cbegin() const ENTT_NOEXCEPT {
+        return view.cbegin();
+    }
+
+    /**
+     * @brief Returns an iterator to the first entity that has the given
+     * components.
+     *
+     * The returned iterator points to the first entity that has the given
+     * components. If the view is empty, the returned iterator will be equal to
+     * `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the first entity that has the given components.
+     */
+    iterator_type begin() ENTT_NOEXCEPT {
         return view.begin();
     }
 
@@ -146,7 +174,26 @@ public:
      * @return An iterator to the entity following the last entity that has the
      * given components.
      */
-    iterator_type end() const ENTT_NOEXCEPT {
+    const_iterator_type cend() const ENTT_NOEXCEPT {
+        return view.cend();
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last entity that has the
+     * given components.
+     *
+     * The returned iterator points to the entity following the last entity that
+     * has the given components. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the entity following the last entity that has the
+     * given components.
+     */
+    iterator_type end() ENTT_NOEXCEPT {
         return view.end();
     }
 
@@ -270,9 +317,9 @@ public:
      */
     template<typename Func>
     void each(Func func) const {
-        for(auto entity: view) {
-            func(entity, get<Component>(entity)...);
-        }
+        std::for_each(view.cbegin(), view.cend(), [&func, this](const auto entity) {
+            func(entity, std::get<pool_type<Component> &>(pools).get(entity)...);
+        });
     }
 
     /**
@@ -293,8 +340,8 @@ public:
      */
     template<typename Func>
     void each(Func func) {
-        const_cast<const PersistentView *>(this)->each([&func](entity_type entity, const Component &... component) {
-            func(entity, const_cast<Component &>(component)...);
+        std::for_each(view.begin(), view.end(), [&func, this](const auto entity) {
+            func(entity, std::get<pool_type<Component> &>(pools).get(entity)...);
         });
     }
 
@@ -376,7 +423,7 @@ class View final {
     using pool_type = SparseSet<Entity, Comp>;
 
     using view_type = SparseSet<Entity>;
-    using underlying_iterator_type = typename view_type::iterator_type;
+    using underlying_iterator_type = typename view_type::const_iterator_type;
     using unchecked_type = std::array<const view_type *, (sizeof...(Component) - 1)>;
     using pattern_type = std::tuple<pool_type<Component> &...>;
     using traits_type = entt_traits<Entity>;
@@ -459,6 +506,8 @@ class View final {
 public:
     /*! @brief Input iterator type. */
     using iterator_type = Iterator;
+    /*! @brief Constant input iterator type. */
+    using const_iterator_type = Iterator;
     /*! @brief Underlying entity identifier. */
     using entity_type = typename view_type::entity_type;
     /*! @brief Unsigned integer type. */
@@ -472,6 +521,14 @@ public:
         return view->size();
     }
 
+    /**
+     * @brief Checks if the view is definitely empty.
+     * @return True if the view is definitely empty, false otherwise.
+     */
+    bool empty() const ENTT_NOEXCEPT {
+        return view->empty();
+    }
+
     /**
      * @brief Returns an iterator to the first entity that has the given
      * components.
@@ -486,9 +543,27 @@ public:
      *
      * @return An iterator to the first entity that has the given components.
      */
-    iterator_type begin() const ENTT_NOEXCEPT {
+    const_iterator_type cbegin() const ENTT_NOEXCEPT {
         const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
-        return Iterator{unchecked, extent, view->begin(), view->end()};
+        return iterator_type{ unchecked, extent, view->cbegin(), view->cend() };
+    }
+
+    /**
+     * @brief Returns an iterator to the first entity that has the given
+     * components.
+     *
+     * The returned iterator points to the first entity that has the given
+     * components. If the view is empty, the returned iterator will be equal to
+     * `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the first entity that has the given components.
+     */
+    inline iterator_type begin() ENTT_NOEXCEPT {
+        return cbegin();
     }
 
     /**
@@ -506,9 +581,28 @@ public:
      * @return An iterator to the entity following the last entity that has the
      * given components.
      */
-    iterator_type end() const ENTT_NOEXCEPT {
+    const_iterator_type cend() const ENTT_NOEXCEPT {
         const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
-        return Iterator{unchecked, extent, view->end(), view->end()};
+        return iterator_type{ unchecked, extent, view->cend(), view->cend() };
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last entity that has the
+     * given components.
+     *
+     * The returned iterator points to the entity following the last entity that
+     * has the given components. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the entity following the last entity that has the
+     * given components.
+     */
+    inline iterator_type end() ENTT_NOEXCEPT {
+        return cend();
     }
 
     /**
@@ -641,7 +735,7 @@ public:
     void each(Func func) const {
         const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
 
-        for(auto entity: *view) {
+        std::for_each(view->cbegin(), view->cend(), [&func, extent, this](const auto entity) {
             const auto sz = size_type(entity & traits_type::entity_mask);
 
             if(sz < extent) {
@@ -650,10 +744,10 @@ public:
                 for(; pos && unchecked[pos-1]->fast(entity); --pos);
 
                 if(!pos) {
-                    func(entity, get<Component>(entity)...);
+                    func(entity, std::get<pool_type<Component> &>(pools).get(entity)...);
                 }
             }
-        }
+        });
     }
 
     /**
@@ -768,6 +862,8 @@ class View<Entity, Component> final {
 public:
     /*! @brief Input iterator type. */
     using iterator_type = typename view_type::iterator_type;
+    /*! @brief Constant input iterator type. */
+    using const_iterator_type = typename view_type::const_iterator_type;
     /*! @brief Underlying entity identifier. */
     using entity_type = typename pool_type::entity_type;
     /*! @brief Unsigned integer type. */
@@ -783,6 +879,14 @@ public:
         return pool.size();
     }
 
+    /**
+     * @brief Checks whether the view is empty.
+     * @return True if the view is empty, false otherwise.
+     */
+    bool empty() const ENTT_NOEXCEPT {
+        return pool.empty();
+    }
+
     /**
      * @brief Direct access to the list of components.
      *
@@ -795,7 +899,7 @@ public:
      *
      * @return A pointer to the array of components.
      */
-    raw_type * raw() ENTT_NOEXCEPT {
+    const raw_type * raw() const ENTT_NOEXCEPT {
         return pool.raw();
     }
 
@@ -811,8 +915,8 @@ public:
      *
      * @return A pointer to the array of components.
      */
-    const raw_type * raw() const ENTT_NOEXCEPT {
-        return pool.raw();
+    raw_type * raw() ENTT_NOEXCEPT {
+        return const_cast<raw_type *>(const_cast<const View *>(this)->raw());
     }
 
     /**
@@ -845,7 +949,25 @@ public:
      *
      * @return An iterator to the first entity that has the given component.
      */
-    iterator_type begin() const ENTT_NOEXCEPT {
+    const_iterator_type cbegin() const ENTT_NOEXCEPT {
+        return pool.view_type::cbegin();
+    }
+
+    /**
+     * @brief Returns an iterator to the first entity that has the given
+     * component.
+     *
+     * The returned iterator points to the first entity that has the given
+     * component. If the view is empty, the returned iterator will be equal to
+     * `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the first entity that has the given component.
+     */
+    iterator_type begin() ENTT_NOEXCEPT {
         return pool.view_type::begin();
     }
 
@@ -864,7 +986,26 @@ public:
      * @return An iterator to the entity following the last entity that has the
      * given component.
      */
-    iterator_type end() const ENTT_NOEXCEPT {
+    const_iterator_type cend() const ENTT_NOEXCEPT {
+        return pool.view_type::cend();
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last entity that has the
+     * given component.
+     *
+     * The returned iterator points to the entity following the last entity that
+     * has the given component. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the entity following the last entity that has the
+     * given component.
+     */
+    iterator_type end() ENTT_NOEXCEPT {
         return pool.view_type::end();
     }
 
@@ -933,11 +1074,9 @@ public:
      */
     template<typename Func>
     void each(Func func) const {
-        const view_type &view = pool;
-
-        for(auto entity: view) {
-            func(entity, get(entity));
-        }
+        std::for_each(pool.view_type::cbegin(), pool.view_type::cend(), [&func, this](const auto entity) {
+            func(entity, pool.get(entity));
+        });
     }
 
     /**
@@ -957,8 +1096,8 @@ public:
      */
     template<typename Func>
     void each(Func func) {
-        const_cast<const View *>(this)->each([&func](entity_type entity, const Component &component) {
-            func(entity, const_cast<Component &>(component));
+        std::for_each(pool.view_type::begin(), pool.view_type::end(), [&func, this](const auto entity) {
+            func(entity, pool.get(entity));
         });
     }
 
@@ -1010,7 +1149,6 @@ class RawView final {
     /*! @brief A registry is allowed to create views. */
     friend class Registry<Entity>;
 
-    using view_type = SparseSet<Entity>;
     using pool_type = SparseSet<Entity, Component>;
 
     RawView(pool_type &pool) ENTT_NOEXCEPT
@@ -1020,6 +1158,8 @@ class RawView final {
 public:
     /*! @brief Input iterator type. */
     using iterator_type = typename pool_type::iterator_type;
+    /*! @brief Constant input iterator type. */
+    using const_iterator_type = typename pool_type::const_iterator_type;
     /*! @brief Underlying entity identifier. */
     using entity_type = typename pool_type::entity_type;
     /*! @brief Unsigned integer type. */
@@ -1035,6 +1175,14 @@ public:
         return pool.size();
     }
 
+    /**
+     * @brief Checks whether the view is empty.
+     * @return True if the view is empty, false otherwise.
+     */
+    bool empty() const ENTT_NOEXCEPT {
+        return pool.empty();
+    }
+
     /**
      * @brief Direct access to the list of components.
      *
@@ -1047,7 +1195,7 @@ public:
      *
      * @return A pointer to the array of components.
      */
-    raw_type * raw() ENTT_NOEXCEPT {
+    const raw_type * raw() const ENTT_NOEXCEPT {
         return pool.raw();
     }
 
@@ -1063,8 +1211,8 @@ public:
      *
      * @return A pointer to the array of components.
      */
-    const raw_type * raw() const ENTT_NOEXCEPT {
-        return pool.raw();
+    raw_type * raw() ENTT_NOEXCEPT {
+        return const_cast<raw_type *>(const_cast<const RawView *>(this)->raw());
     }
 
     /**
@@ -1095,7 +1243,23 @@ public:
      *
      * @return An iterator to the first instance of the given type.
      */
-    iterator_type begin() const ENTT_NOEXCEPT {
+    const_iterator_type cbegin() const ENTT_NOEXCEPT {
+        return pool.cbegin();
+    }
+
+    /**
+     * @brief Returns an iterator to the first instance of the given type.
+     *
+     * The returned iterator points to the first instance of the given type. If
+     * the view is empty, the returned iterator will be equal to `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the first instance of the given type.
+     */
+    iterator_type begin() ENTT_NOEXCEPT {
         return pool.begin();
     }
 
@@ -1114,10 +1278,67 @@ public:
      * @return An iterator to the element following the last instance of the
      * given type.
      */
-    iterator_type end() const ENTT_NOEXCEPT {
+    const_iterator_type cend() const ENTT_NOEXCEPT {
+        return pool.cend();
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last instance of the given
+     * type.
+     *
+     * The returned iterator points to the element following the last instance
+     * of the given type. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the element following the last instance of the
+     * given type.
+     */
+    iterator_type end() ENTT_NOEXCEPT {
         return pool.end();
     }
 
+    /**
+     * @brief Iterates components and applies the given function object to them.
+     *
+     * The function object is provided with a const reference to each component
+     * of the view.<br/>
+     * The signature of the function should be equivalent to the following:
+     *
+     * @code{.cpp}
+     * void(const Component &);
+     * @endcode
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void each(Func func) const {
+        std::for_each(pool.cbegin(), pool.cend(), func);
+    }
+
+    /**
+     * @brief Iterates components and applies the given function object to them.
+     *
+     * The function object is provided with a const reference to each component
+     * of the view.<br/>
+     * The signature of the function should be equivalent to the following:
+     *
+     * @code{.cpp}
+     * void(const Component &);
+     * @endcode
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void each(Func func) {
+        std::for_each(pool.begin(), pool.end(), func);
+    }
+
 private:
     pool_type &pool;
 };

+ 30 - 0
test/entt/entity/registry.cpp

@@ -431,6 +431,24 @@ TEST(DefaultRegistry, PersistentViews) {
     ASSERT_EQ(cnt, decltype(view)::size_type{2});
 }
 
+TEST(DefaultRegistry, RawViews) {
+    entt::DefaultRegistry registry;
+    auto view = registry.raw<int>();
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 0);
+    registry.assign<char>(e0, 'c');
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 0);
+    registry.assign<char>(e1, 'c');
+
+    decltype(view)::size_type cnt{0};
+    view.each([&cnt](auto &...) { ++cnt; });
+
+    ASSERT_EQ(cnt, decltype(view)::size_type{2});
+}
+
 TEST(DefaultRegistry, CleanStandardViewsAfterReset) {
     entt::DefaultRegistry registry;
     auto view = registry.view<int>();
@@ -458,6 +476,18 @@ TEST(DefaultRegistry, CleanPersistentViewsAfterReset) {
     ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
 }
 
+TEST(DefaultRegistry, CleanRawViewsAfterReset) {
+    entt::DefaultRegistry registry;
+    auto view = registry.raw<int>();
+    registry.assign<int>(registry.create(), 0);
+
+    ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
+
+    registry.reset();
+
+    ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
+}
+
 TEST(DefaultRegistry, CleanTagsAfterReset) {
     entt::DefaultRegistry registry;
     const auto entity = registry.create();

+ 27 - 10
test/entt/entity/view.cpp

@@ -4,20 +4,22 @@
 
 TEST(View, SingleComponent) {
     entt::DefaultRegistry registry;
+    auto view = registry.view<char>();
 
     const auto e0 = registry.create();
     const auto e1 = registry.create();
 
+    ASSERT_TRUE(view.empty());
+
     registry.assign<int>(e1);
     registry.assign<char>(e1);
 
     ASSERT_NO_THROW(registry.view<char>().begin()++);
     ASSERT_NO_THROW(++registry.view<char>().begin());
 
-    auto view = registry.view<char>();
-
     ASSERT_NE(view.begin(), view.end());
     ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
+    ASSERT_FALSE(view.empty());
 
     registry.assign<char>(e0);
 
@@ -41,6 +43,7 @@ TEST(View, SingleComponent) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
+    ASSERT_TRUE(view.empty());
 }
 
 TEST(View, SingleComponentContains) {
@@ -106,12 +109,18 @@ TEST(View, SingleComponentEach) {
 
 TEST(View, MultipleComponent) {
     entt::DefaultRegistry registry;
+    auto view = registry.view<int, char>();
+
+    ASSERT_TRUE(view.empty());
 
     const auto e0 = registry.create();
     registry.assign<char>(e0);
 
     const auto e1 = registry.create();
     registry.assign<int>(e1);
+
+    ASSERT_FALSE(view.empty());
+
     registry.assign<char>(e1);
 
     auto it = registry.view<char>().begin();
@@ -123,8 +132,6 @@ TEST(View, MultipleComponent) {
     ASSERT_NO_THROW((registry.view<int, char>().begin()++));
     ASSERT_NO_THROW((++registry.view<int, char>().begin()));
 
-    auto view = registry.view<int, char>();
-
     ASSERT_NE(view.begin(), view.end());
     ASSERT_EQ(view.begin()+1, view.end());
     ASSERT_EQ(view.size(), decltype(view.size()){1});
@@ -145,6 +152,7 @@ TEST(View, MultipleComponent) {
     view.reset();
 
     ASSERT_EQ(view.begin(), view.end());
+    ASSERT_TRUE(view.empty());
 }
 
 TEST(View, MultipleComponentContains) {
@@ -213,6 +221,9 @@ TEST(View, MultipleComponentEach) {
 TEST(PersistentView, Prepare) {
     entt::DefaultRegistry registry;
     registry.prepare<int, char>();
+    auto view = registry.persistent<int, char>();
+
+    ASSERT_TRUE(view.empty());
 
     const auto e0 = registry.create();
     registry.assign<char>(e0);
@@ -221,11 +232,10 @@ TEST(PersistentView, Prepare) {
     registry.assign<int>(e1);
     registry.assign<char>(e1);
 
+    ASSERT_FALSE(view.empty());
     ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
     ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
 
-    auto view = registry.persistent<int, char>();
-
     ASSERT_NE(view.begin(), view.end());
     ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
 
@@ -254,10 +264,14 @@ TEST(PersistentView, Prepare) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
+    ASSERT_TRUE(view.empty());
 }
 
 TEST(PersistentView, NoPrepare) {
     entt::DefaultRegistry registry;
+    auto view = registry.persistent<int, char>();
+
+    ASSERT_TRUE(view.empty());
 
     const auto e0 = registry.create();
     registry.assign<char>(e0);
@@ -266,11 +280,10 @@ TEST(PersistentView, NoPrepare) {
     registry.assign<int>(e1);
     registry.assign<char>(e1);
 
+    ASSERT_FALSE(view.empty());
     ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
     ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
 
-    auto view = registry.persistent<int, char>();
-
     ASSERT_NE(view.begin(), view.end());
     ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
 
@@ -299,6 +312,7 @@ TEST(PersistentView, NoPrepare) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
+    ASSERT_TRUE(view.empty());
 }
 
 TEST(PersistentView, Contains) {
@@ -405,6 +419,9 @@ TEST(PersistentView, Sort) {
 
 TEST(RawView, Functionalities) {
     entt::DefaultRegistry registry;
+    auto view = registry.raw<char>();
+
+    ASSERT_TRUE(view.empty());
 
     const auto e0 = registry.create();
     const auto e1 = registry.create();
@@ -412,11 +429,10 @@ TEST(RawView, Functionalities) {
     registry.assign<int>(e1);
     registry.assign<char>(e1);
 
+    ASSERT_FALSE(view.empty());
     ASSERT_NO_THROW(registry.raw<char>().begin()++);
     ASSERT_NO_THROW(++registry.raw<char>().begin());
 
-    auto view = registry.raw<char>();
-
     ASSERT_NE(view.begin(), view.end());
     ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
 
@@ -450,6 +466,7 @@ TEST(RawView, Functionalities) {
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
+    ASSERT_TRUE(view.empty());
 }
 
 TEST(RawView, Empty) {