فهرست منبع

view/group: safe invalid views/groups (close #470)

Michele Caini 5 سال پیش
والد
کامیت
60475d3354
7فایلهای تغییر یافته به همراه307 افزوده شده و 130 حذف شده
  1. 2 0
      TODO
  2. 36 7
      docs/md/entity.md
  3. 66 59
      src/entt/entity/group.hpp
  4. 67 59
      src/entt/entity/view.hpp
  5. 66 0
      test/entt/entity/group.cpp
  6. 5 5
      test/entt/entity/registry.cpp
  7. 65 0
      test/entt/entity/view.cpp

+ 2 - 0
TODO

@@ -25,7 +25,9 @@ WIP:
 * add exclude-only views to combine with packs
 * add exclude-only views to combine with packs
 * deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
 * deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
 * HP: write documentation for custom storages and views!!
 * HP: write documentation for custom storages and views!!
+* view pack: optimize it and iterate only first min size hint entities
 * view pack: plain function as an alias for operator|, reverse iterators, rbegin and rend
 * view pack: plain function as an alias for operator|, reverse iterators, rbegin and rend
+* what about using hashed string rather than hash values for meta types?
 * pagination doesn't work nicely across boundaries probably, give it a look. RO operations are fine, adding components maybe not.
 * 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
 * make it easier to hook into the type system and describe how to do that to eg auto-generate meta types on first use
 * add observer functions aside observer class
 * add observer functions aside observer class

+ 36 - 7
docs/md/entity.md

@@ -36,12 +36,13 @@
 * [Views and Groups](#views-and-groups)
 * [Views and Groups](#views-and-groups)
   * [Views](#views)
   * [Views](#views)
     * [View pack](#view-pack)
     * [View pack](#view-pack)
-  * [Runtime views](#runtime-views)
+    * [Runtime views](#runtime-views)
   * [Groups](#groups)
   * [Groups](#groups)
     * [Full-owning groups](#full-owning-groups)
     * [Full-owning groups](#full-owning-groups)
     * [Partial-owning groups](#partial-owning-groups)
     * [Partial-owning groups](#partial-owning-groups)
     * [Non-owning groups](#non-owning-groups)
     * [Non-owning groups](#non-owning-groups)
     * [Nested groups](#nested-groups)
     * [Nested groups](#nested-groups)
+  * [Invalid views and groups](#invalid-views-and-groups)
   * [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between)
   * [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between)
   * [Give me everything](#give-me-everything)
   * [Give me everything](#give-me-everything)
   * [What is allowed and what is not](#what-is-allowed-and-what-is-not)
   * [What is allowed and what is not](#what-is-allowed-and-what-is-not)
@@ -1320,8 +1321,8 @@ a multi component view exposes utility functions to get the estimated number of
 entities it is going to return and to know if it contains a given entity.<br/>
 entities it is going to return and to know if it contains a given entity.<br/>
 Refer to the inline documentation for all the details.
 Refer to the inline documentation for all the details.
 
 
-There is no need to store views around for they are extremely cheap to
-construct, even though they can be copied without problems and reused freely.
+There is no need to store views aside for they are extremely cheap to construct,
+even though valid views can be copied without problems and reused freely.<br/>
 Views also return newly created and correctly initialized iterators whenever
 Views also return newly created and correctly initialized iterators whenever
 `begin` or `end` are invoked.
 `begin` or `end` are invoked.
 
 
@@ -1459,7 +1460,7 @@ inherited by the views that compose it:
 Read also the dedicated section to know how a view pack is involved in the
 Read also the dedicated section to know how a view pack is involved in the
 creation and use of custom storage and pools.
 creation and use of custom storage and pools.
 
 
-## Runtime views
+### Runtime views
 
 
 Runtime views iterate entities that have at least all the given components in
 Runtime views iterate entities that have at least all the given components in
 their bags. During construction, these views look at the number of entities
 their bags. During construction, these views look at the number of entities
@@ -1473,7 +1474,7 @@ going to return and to know whether it's empty or not. It's also possible to ask
 a runtime view if it contains a given entity.<br/>
 a runtime view if it contains a given entity.<br/>
 Refer to the inline documentation for all the details.
 Refer to the inline documentation for all the details.
 
 
-Runtime views are pretty cheap to construct and should not be stored around in
+Runtime views are pretty cheap to construct and should not be stored aside in
 any case. They should be used immediately after creation and then they should be
 any case. They should be used immediately after creation and then they should be
 thrown away. The reasons for this go far beyond the scope of this document.<br/>
 thrown away. The reasons for this go far beyond the scope of this document.<br/>
 To iterate a runtime view, either use it in a range-for loop:
 To iterate a runtime view, either use it in a range-for loop:
@@ -1545,8 +1546,9 @@ list for owned components. It's also possible to ask a group if it contains a
 given entity.<br/>
 given entity.<br/>
 Refer to the inline documentation for all the details.
 Refer to the inline documentation for all the details.
 
 
-There is no need to store groups around for they are extremely cheap to
-construct, even though they can be copied without problems and reused freely.
+There is no need to store groups aside for they are extremely cheap to
+construct, even though valid groups can be copied without problems and reused
+freely.<br/>
 A group performs an initialization step the very first time it's requested and
 A group performs an initialization step the very first time it's requested and
 this could be quite costly. To avoid it, consider creating the group when no
 this could be quite costly. To avoid it, consider creating the group when no
 components have been assigned yet. If the registry is empty, preparation is
 components have been assigned yet. If the registry is empty, preparation is
@@ -1758,6 +1760,33 @@ restrictive of them. To prevent users from having to remember which of their
 groups is the most restrictive, the registry class offers the `sortable` member
 groups is the most restrictive, the registry class offers the `sortable` member
 function to know if a group can be sorted or not.
 function to know if a group can be sorted or not.
 
 
+## Invalid views and groups
+
+Views and groups as returned by a registry are generally valid. However, there
+are some exceptions where an invalid object might be returned.<br/>
+In these cases, they should be renewed as soon as possible. In fact, an invalid
+view or groups contains a broken reference to one or more pools and this will
+never be fixed. The view or the group will continue to return no data, even if
+the pool for the pending reference is created in the registry in the meantime.
+
+There is only one case in which an invalid object can be returned, that is when
+the view or the groups is created from a constant reference to a registry in
+which the required pools haven't yet been created.<br/>
+Pools are typically created whenever any method is used on a non-const registry.
+This also means that creating views and groups from a non-const registry can
+never result in an invalid object.
+
+It's also perfectly fine to use an invalid view or group, to invoke `each` on
+them or to iterate them like any other object. The only difference from a valid
+view or group is that the invalid ones will always appear as _empty_.<br/>
+In general, when views and groups are created on the fly and used at the same
+time, then discarded immediately afterwards, it shouldn't even worry about
+whether or not they may be invalid. Therefore, this remains the recommended
+approach.
+
+To know if a view or a group is properly initialized, both can be converted to
+bool explicitly and used in a guard.
+
 ## Types: const, non-const and all in between
 ## Types: const, non-const and all in between
 
 
 The `registry` class offers two overloads when it comes to constructing views
 The `registry` class offers two overloads when it comes to constructing views

+ 66 - 59
src/entt/entity/group.hpp

@@ -118,8 +118,8 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>> final {
             const std::tuple<storage_type<Get> *...> pools;
             const std::tuple<storage_type<Get> *...> pools;
         };
         };
 
 
-        iterable_group(basic_sparse_set<Entity> &ref, const std::tuple<storage_type<Get> *...> &cpools)
-            : handler{&ref},
+        iterable_group(basic_sparse_set<Entity> * const ref, const std::tuple<storage_type<Get> *...> &cpools)
+            : handler{ref},
               pools{cpools}
               pools{cpools}
         {}
         {}
 
 
@@ -128,23 +128,23 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>> final {
         using reverse_iterator = iterable_group_iterator<typename basic_sparse_set<Entity>::reverse_iterator>;
         using reverse_iterator = iterable_group_iterator<typename basic_sparse_set<Entity>::reverse_iterator>;
 
 
         [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
         [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-            return { handler->begin(), pools };
+            return handler ? iterator{handler->begin(), pools} : iterator{{}, pools};
         }
         }
 
 
         [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
         [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
-            return { handler->end(), pools };
+            return handler ? iterator{handler->end(), pools} : iterator{{}, pools};
         }
         }
 
 
         [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
         [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
-            return { handler->rbegin(), pools };
+            return handler ? reverse_iterator{handler->rbegin(), pools} : reverse_iterator{{}, pools};
         }
         }
 
 
         [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
         [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
-            return { handler->rend(), pools };
+            return handler ? reverse_iterator{handler->rend(), pools} : reverse_iterator{{}, pools};
         }
         }
 
 
     private:
     private:
-        basic_sparse_set<Entity> *handler;
+        basic_sparse_set<Entity> * const handler;
         const std::tuple<storage_type<Get> *...> pools;
         const std::tuple<storage_type<Get> *...> pools;
     };
     };
 
 
@@ -173,7 +173,7 @@ public:
      * @return Number of entities that have the given components.
      * @return Number of entities that have the given components.
      */
      */
     [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
     [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
-        return handler->size();
+        return *this ? handler->size() : size_type{};
     }
     }
 
 
     /**
     /**
@@ -182,12 +182,14 @@ public:
      * @return Capacity of the group.
      * @return Capacity of the group.
      */
      */
     [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
     [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
-        return handler->capacity();
+        return *this ? handler->capacity() : size_type{};
     }
     }
 
 
     /*! @brief Requests the removal of unused capacity. */
     /*! @brief Requests the removal of unused capacity. */
     void shrink_to_fit() {
     void shrink_to_fit() {
-        handler->shrink_to_fit();
+        if(*this) {
+            handler->shrink_to_fit();
+        }
     }
     }
 
 
     /**
     /**
@@ -195,7 +197,7 @@ public:
      * @return True if the group is empty, false otherwise.
      * @return True if the group is empty, false otherwise.
      */
      */
     [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
     [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
-        return handler->empty();
+        return !*this || handler->empty();
     }
     }
 
 
     /**
     /**
@@ -207,7 +209,7 @@ public:
      * @return A pointer to the array of entities.
      * @return A pointer to the array of entities.
      */
      */
     [[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
     [[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
-        return handler->data();
+        return *this ? handler->data() : nullptr;
     }
     }
 
 
     /**
     /**
@@ -219,7 +221,7 @@ public:
      * @return An iterator to the first entity of the group.
      * @return An iterator to the first entity of the group.
      */
      */
     [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
     [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-        return handler->begin();
+        return *this ? handler->begin() : iterator{};
     }
     }
 
 
     /**
     /**
@@ -233,7 +235,7 @@ public:
      * group.
      * group.
      */
      */
     [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
     [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
-        return handler->end();
+        return *this ? handler->end() : iterator{};
     }
     }
 
 
     /**
     /**
@@ -245,7 +247,7 @@ public:
      * @return An iterator to the first entity of the reversed group.
      * @return An iterator to the first entity of the reversed group.
      */
      */
     [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
     [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
-        return handler->rbegin();
+        return *this ? handler->rbegin() : reverse_iterator{};
     }
     }
 
 
     /**
     /**
@@ -260,7 +262,7 @@ public:
      * reversed group.
      * reversed group.
      */
      */
     [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
     [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
-        return handler->rend();
+        return *this ? handler->rend() : reverse_iterator{};
     }
     }
 
 
     /**
     /**
@@ -290,7 +292,7 @@ public:
      * iterator otherwise.
      * iterator otherwise.
      */
      */
     [[nodiscard]] iterator find(const entity_type entt) const {
     [[nodiscard]] iterator find(const entity_type entt) const {
-        const auto it = handler->find(entt);
+        const auto it = *this ? handler->find(entt) : iterator{};
         return it != end() && *it == entt ? it : end();
         return it != end() && *it == entt ? it : end();
     }
     }
 
 
@@ -317,7 +319,7 @@ public:
      * @return True if the group contains the given entity, false otherwise.
      * @return True if the group contains the given entity, false otherwise.
      */
      */
     [[nodiscard]] bool contains(const entity_type entt) const {
     [[nodiscard]] bool contains(const entity_type entt) const {
-        return handler->contains(entt);
+        return *this && handler->contains(entt);
     }
     }
 
 
     /**
     /**
@@ -372,7 +374,7 @@ public:
      */
      */
     template<typename Func>
     template<typename Func>
     void each(Func func) const {
     void each(Func func) const {
-        for(const auto entt: *handler) {
+        for(const auto entt: *this) {
             if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_group>().get({})))>) {
             if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_group>().get({})))>) {
                 std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt)));
                 std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt)));
             } else {
             } else {
@@ -395,7 +397,7 @@ public:
      * @return An iterable object to use to _visit_ the group.
      * @return An iterable object to use to _visit_ the group.
      */
      */
     [[nodiscard]] iterable_group each() const ENTT_NOEXCEPT {
     [[nodiscard]] iterable_group each() const ENTT_NOEXCEPT {
-        return iterable_group{*handler, pools};
+        return iterable_group{handler, pools};
     }
     }
 
 
     /**
     /**
@@ -436,17 +438,19 @@ public:
      */
      */
     template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
     template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
     void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
     void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
-        if constexpr(sizeof...(Component) == 0) {
-            static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
-            handler->sort(std::move(compare), std::move(algo), std::forward<Args>(args)...);
-        }  else if constexpr(sizeof...(Component) == 1) {
-            handler->sort([this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
-                return compare((std::get<storage_type<Component> *>(pools)->get(lhs), ...), (std::get<storage_type<Component> *>(pools)->get(rhs), ...));
-            }, std::move(algo), std::forward<Args>(args)...);
-        } else {
-            handler->sort([this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
-                return compare(std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(rhs)...));
-            }, std::move(algo), std::forward<Args>(args)...);
+        if(*this) {
+            if constexpr(sizeof...(Component) == 0) {
+                static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
+                handler->sort(std::move(compare), std::move(algo), std::forward<Args>(args)...);
+            }  else if constexpr(sizeof...(Component) == 1) {
+                handler->sort([this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
+                    return compare((std::get<storage_type<Component> *>(pools)->get(lhs), ...), (std::get<storage_type<Component> *>(pools)->get(rhs), ...));
+                }, std::move(algo), std::forward<Args>(args)...);
+            } else {
+                handler->sort([this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
+                    return compare(std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(rhs)...));
+                }, std::move(algo), std::forward<Args>(args)...);
+            }
         }
         }
     }
     }
 
 
@@ -468,11 +472,13 @@ public:
      */
      */
     template<typename Component>
     template<typename Component>
     void sort() const {
     void sort() const {
-        handler->respect(*std::get<storage_type<Component> *>(pools));
+        if(*this) {
+            handler->respect(*std::get<storage_type<Component> *>(pools));
+        }
     }
     }
 
 
 private:
 private:
-    basic_sparse_set<entity_type> *handler;
+    basic_sparse_set<entity_type> * const handler;
     const std::tuple<storage_type<Get> *...> pools;
     const std::tuple<storage_type<Get> *...> pools;
 };
 };
 
 
@@ -586,9 +592,9 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
             std::tuple<storage_type<Get> *...> get;
             std::tuple<storage_type<Get> *...> get;
         };
         };
 
 
-        iterable_group(std::tuple<storage_type<Owned> *..., storage_type<Get> *...> cpools, const std::size_t &extent)
+        iterable_group(std::tuple<storage_type<Owned> *..., storage_type<Get> *...> cpools, const std::size_t * const extent)
             : pools{cpools},
             : pools{cpools},
-              length{&extent}
+              length{extent}
         {}
         {}
 
 
     public:
     public:
@@ -602,40 +608,40 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
         >;
         >;
 
 
         [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
         [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-            return {
+            return length ? iterator{
                 std::get<0>(pools)->basic_sparse_set<Entity>::end() - *length,
                 std::get<0>(pools)->basic_sparse_set<Entity>::end() - *length,
                 std::make_tuple((std::get<storage_type<Owned> *>(pools)->end() - *length)...),
                 std::make_tuple((std::get<storage_type<Owned> *>(pools)->end() - *length)...),
                 std::make_tuple(std::get<storage_type<Get> *>(pools)...)
                 std::make_tuple(std::get<storage_type<Get> *>(pools)...)
-            };
+            } : iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->end()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
         }
         }
 
 
         [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
         [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
-            return {
+            return length ? iterator{
                 std::get<0>(pools)->basic_sparse_set<Entity>::end(),
                 std::get<0>(pools)->basic_sparse_set<Entity>::end(),
                 std::make_tuple((std::get<storage_type<Owned> *>(pools)->end())...),
                 std::make_tuple((std::get<storage_type<Owned> *>(pools)->end())...),
                 std::make_tuple(std::get<storage_type<Get> *>(pools)...)
                 std::make_tuple(std::get<storage_type<Get> *>(pools)...)
-            };
+            } : iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->end()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
         }
         }
 
 
         [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
         [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
-            return {
+            return length ? reverse_iterator{
                 std::get<0>(pools)->basic_sparse_set<Entity>::rbegin(),
                 std::get<0>(pools)->basic_sparse_set<Entity>::rbegin(),
                 std::make_tuple((std::get<storage_type<Owned> *>(pools)->rbegin())...),
                 std::make_tuple((std::get<storage_type<Owned> *>(pools)->rbegin())...),
                 std::make_tuple(std::get<storage_type<Get> *>(pools)...)
                 std::make_tuple(std::get<storage_type<Get> *>(pools)...)
-            };
+            } : reverse_iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->rbegin()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
         }
         }
 
 
         [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
         [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
-            return {
+            return length ? reverse_iterator{
                 std::get<0>(pools)->basic_sparse_set<Entity>::rbegin() + *length,
                 std::get<0>(pools)->basic_sparse_set<Entity>::rbegin() + *length,
                 std::make_tuple((std::get<storage_type<Owned> *>(pools)->rbegin() + *length)...),
                 std::make_tuple((std::get<storage_type<Owned> *>(pools)->rbegin() + *length)...),
                 std::make_tuple(std::get<storage_type<Get> *>(pools)...)
                 std::make_tuple(std::get<storage_type<Get> *>(pools)...)
-            };
+            } : reverse_iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->rbegin()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
         }
         }
 
 
     private:
     private:
         const std::tuple<storage_type<Owned> *..., storage_type<Get> *...> pools;
         const std::tuple<storage_type<Owned> *..., storage_type<Get> *...> pools;
-        const std::size_t *length;
+        const std::size_t * const length;
     };
     };
 
 
     basic_group(const std::size_t &extent, storage_type<Owned> &... opool, storage_type<Get> &... gpool) ENTT_NOEXCEPT
     basic_group(const std::size_t &extent, storage_type<Owned> &... opool, storage_type<Get> &... gpool) ENTT_NOEXCEPT
@@ -663,7 +669,7 @@ public:
      * @return Number of entities that have the given components.
      * @return Number of entities that have the given components.
      */
      */
     [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
     [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
-        return *length;
+        return *this ? *length : size_type{};
     }
     }
 
 
     /**
     /**
@@ -671,7 +677,7 @@ public:
      * @return True if the group is empty, false otherwise.
      * @return True if the group is empty, false otherwise.
      */
      */
     [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
     [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
-        return !*length;
+        return !*this || !*length;
     }
     }
 
 
     /**
     /**
@@ -690,7 +696,8 @@ public:
     template<typename Component>
     template<typename Component>
     [[nodiscard]] Component * raw() const ENTT_NOEXCEPT {
     [[nodiscard]] Component * raw() const ENTT_NOEXCEPT {
         static_assert((std::is_same_v<Component, Owned> || ...));
         static_assert((std::is_same_v<Component, Owned> || ...));
-        return std::get<storage_type<Component> *>(pools)->raw();
+        auto *cpool = std::get<storage_type<Component> *>(pools);
+        return cpool ? cpool->raw() : nullptr;
     }
     }
 
 
     /**
     /**
@@ -702,7 +709,7 @@ public:
      * @return A pointer to the array of entities.
      * @return A pointer to the array of entities.
      */
      */
     [[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
     [[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
-        return std::get<0>(pools)->data();
+        return *this ? std::get<0>(pools)->data() : nullptr;
     }
     }
 
 
     /**
     /**
@@ -714,7 +721,7 @@ public:
      * @return An iterator to the first entity of the group.
      * @return An iterator to the first entity of the group.
      */
      */
     [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
     [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-        return std::get<0>(pools)->basic_sparse_set<entity_type>::end() - *length;
+        return *this ? (std::get<0>(pools)->basic_sparse_set<entity_type>::end() - *length) : iterator{};
     }
     }
 
 
     /**
     /**
@@ -728,7 +735,7 @@ public:
      * group.
      * group.
      */
      */
     [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
     [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
-        return std::get<0>(pools)->basic_sparse_set<entity_type>::end();
+        return *this ? std::get<0>(pools)->basic_sparse_set<entity_type>::end() : iterator{};
     }
     }
 
 
     /**
     /**
@@ -740,7 +747,7 @@ public:
      * @return An iterator to the first entity of the reversed group.
      * @return An iterator to the first entity of the reversed group.
      */
      */
     [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
     [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
-        return std::get<0>(pools)->basic_sparse_set<entity_type>::rbegin();
+        return *this ? std::get<0>(pools)->basic_sparse_set<entity_type>::rbegin() : reverse_iterator{};
     }
     }
 
 
     /**
     /**
@@ -755,7 +762,7 @@ public:
      * reversed group.
      * reversed group.
      */
      */
     [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
     [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
-        return std::get<0>(pools)->basic_sparse_set<entity_type>::rbegin() + *length;
+        return *this ? (std::get<0>(pools)->basic_sparse_set<entity_type>::rbegin() + *length) : reverse_iterator{};
     }
     }
 
 
     /**
     /**
@@ -785,7 +792,7 @@ public:
      * iterator otherwise.
      * iterator otherwise.
      */
      */
     [[nodiscard]] iterator find(const entity_type entt) const {
     [[nodiscard]] iterator find(const entity_type entt) const {
-        const auto it = std::get<0>(pools)->find(entt);
+        const auto it = *this ? std::get<0>(pools)->find(entt) : iterator{};
         return it != end() && it >= begin() && *it == entt ? it : end();
         return it != end() && it >= begin() && *it == entt ? it : end();
     }
     }
 
 
@@ -812,7 +819,7 @@ public:
      * @return True if the group contains the given entity, false otherwise.
      * @return True if the group contains the given entity, false otherwise.
      */
      */
     [[nodiscard]] bool contains(const entity_type entt) const {
     [[nodiscard]] bool contains(const entity_type entt) const {
-        return std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < (*length));
+        return *this && std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < (*length));
     }
     }
 
 
     /**
     /**
@@ -890,7 +897,7 @@ public:
      * @return An iterable object to use to _visit_ the group.
      * @return An iterable object to use to _visit_ the group.
      */
      */
     [[nodiscard]] iterable_group each() const ENTT_NOEXCEPT {
     [[nodiscard]] iterable_group each() const ENTT_NOEXCEPT {
-        return iterable_group{pools, *length};
+        return iterable_group{pools, length};
     }
     }
 
 
     /**
     /**
@@ -931,9 +938,9 @@ public:
      * @param args Arguments to forward to the sort function object, if any.
      * @param args Arguments to forward to the sort function object, if any.
      */
      */
     template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
     template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
-    void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
+    void sort(Compare compare, Sort algo = Sort{}, Args &&... args) const {
         auto *cpool = std::get<0>(pools);
         auto *cpool = std::get<0>(pools);
-
+        
         if constexpr(sizeof...(Component) == 0) {
         if constexpr(sizeof...(Component) == 0) {
             static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
             static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
             cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
             cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
@@ -946,7 +953,7 @@ public:
                 return compare(std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(rhs)...));
                 return compare(std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(rhs)...));
             }, std::move(algo), std::forward<Args>(args)...);
             }, std::move(algo), std::forward<Args>(args)...);
         }
         }
-
+        
         [this](auto *head, auto *... other) {
         [this](auto *head, auto *... other) {
             for(auto next = *length; next; --next) {
             for(auto next = *length; next; --next) {
                 const auto pos = next - 1;
                 const auto pos = next - 1;
@@ -958,7 +965,7 @@ public:
 
 
 private:
 private:
     const std::tuple<storage_type<Owned> *..., storage_type<Get> *...> pools;
     const std::tuple<storage_type<Owned> *..., storage_type<Get> *...> pools;
-    const size_type *length;
+    const size_type * const length;
 };
 };
 
 
 
 

+ 67 - 59
src/entt/entity/view.hpp

@@ -243,33 +243,35 @@ class basic_view<Entity, exclude_t<Exclude...>, Component...> final {
 
 
     template<typename Comp, typename Func>
     template<typename Comp, typename Func>
     void traverse(Func func) const {
     void traverse(Func func) const {
-        if constexpr(std::is_same_v<typename storage_type<Comp>::storage_category, empty_storage_tag>) {
-            for(const auto entt: static_cast<const basic_sparse_set<entity_type> &>(*std::get<storage_type<Comp> *>(pools))) {
-                if(((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
-                    && !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...))
-                {
-                    if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
-                        std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt)));
-                    } else {
-                        std::apply(func, get(entt));
+        if(*this) {
+            if constexpr(std::is_same_v<typename storage_type<Comp>::storage_category, empty_storage_tag>) {
+                for(const auto entt: static_cast<const basic_sparse_set<entity_type> &>(*std::get<storage_type<Comp> *>(pools))) {
+                    if(((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
+                        && !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...))
+                    {
+                        if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
+                            std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt)));
+                        } else {
+                            std::apply(func, get(entt));
+                        }
                     }
                     }
                 }
                 }
-            }
-        } else {
-            auto it = std::get<storage_type<Comp> *>(pools)->begin();
-
-            for(const auto entt: static_cast<const basic_sparse_set<entity_type> &>(*std::get<storage_type<Comp> *>(pools))) {
-                if(((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
-                    && !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...))
-                {
-                    if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
-                        std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Component>(it, entt)...));
-                    } else {
-                        std::apply(func, std::tuple_cat(dispatch_get<Component>(it, entt)...));
+            } else {
+                auto it = std::get<storage_type<Comp> *>(pools)->begin();
+
+                for(const auto entt: static_cast<const basic_sparse_set<entity_type> &>(*std::get<storage_type<Comp> *>(pools))) {
+                    if(((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
+                        && !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...))
+                    {
+                        if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
+                            std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Component>(it, entt)...));
+                        } else {
+                            std::apply(func, std::tuple_cat(dispatch_get<Component>(it, entt)...));
+                        }
                     }
                     }
-                }
 
 
-                ++it;
+                    ++it;
+                }
             }
             }
         }
         }
     }
     }
@@ -306,7 +308,7 @@ public:
      */
      */
     template<typename Comp>
     template<typename Comp>
     void use() const ENTT_NOEXCEPT {
     void use() const ENTT_NOEXCEPT {
-        view = std::get<storage_type<Comp> *>(pools);
+        view = *this ? std::get<storage_type<Comp> *>(pools) : nullptr;
     }
     }
 
 
     /**
     /**
@@ -314,7 +316,7 @@ public:
      * @return Estimated number of entities iterated by the view.
      * @return Estimated number of entities iterated by the view.
      */
      */
     [[nodiscard]] size_type size_hint() const ENTT_NOEXCEPT {
     [[nodiscard]] size_type size_hint() const ENTT_NOEXCEPT {
-        return (std::min)({ std::get<storage_type<Component> *>(pools)->size()... });
+        return *this ? view->size() : size_type{};
     }
     }
 
 
     /**
     /**
@@ -326,7 +328,7 @@ public:
      * @return An iterator to the first entity of the view.
      * @return An iterator to the first entity of the view.
      */
      */
     [[nodiscard]] iterator begin() const {
     [[nodiscard]] iterator begin() const {
-        return { view->begin(), view->end(), view->begin(), unchecked(view), filter };
+        return *this ? iterator{view->begin(), view->end(), view->begin(), unchecked(view), filter} : iterator{};
     }
     }
 
 
     /**
     /**
@@ -339,7 +341,7 @@ public:
      * @return An iterator to the entity following the last entity of the view.
      * @return An iterator to the entity following the last entity of the view.
      */
      */
     [[nodiscard]] iterator end() const {
     [[nodiscard]] iterator end() const {
-        return { view->begin(), view->end(), view->end(), unchecked(view), filter };
+        return *this ? iterator{view->begin(), view->end(), view->end(), unchecked(view), filter} : iterator{};
     }
     }
 
 
     /**
     /**
@@ -351,7 +353,7 @@ public:
      * @return An iterator to the first entity of the reversed view.
      * @return An iterator to the first entity of the reversed view.
      */
      */
     [[nodiscard]] reverse_iterator rbegin() const {
     [[nodiscard]] reverse_iterator rbegin() const {
-        return { view->rbegin(), view->rend(), view->rbegin(), unchecked(view), filter };
+        return *this ? reverse_iterator{view->rbegin(), view->rend(), view->rbegin(), unchecked(view), filter} : reverse_iterator{};
     }
     }
 
 
     /**
     /**
@@ -366,7 +368,7 @@ public:
      * reversed view.
      * reversed view.
      */
      */
     [[nodiscard]] reverse_iterator rend() const {
     [[nodiscard]] reverse_iterator rend() const {
-        return { view->rbegin(), view->rend(), view->rend(), unchecked(view), filter };
+        return *this ? reverse_iterator{view->rbegin(), view->rend(), view->rend(), unchecked(view), filter} : reverse_iterator{};
     }
     }
 
 
     /**
     /**
@@ -396,7 +398,7 @@ public:
      * iterator otherwise.
      * iterator otherwise.
      */
      */
     [[nodiscard]] iterator find(const entity_type entt) const {
     [[nodiscard]] iterator find(const entity_type entt) const {
-        iterator it{view->begin(), view->end(), view->find(entt), unchecked(view), filter};
+        const auto it = *this ? iterator{view->begin(), view->end(), view->find(entt), unchecked(view), filter} : end();
         return (it != end() && *it == entt) ? it : end();
         return (it != end() && *it == entt) ? it : end();
     }
     }
 
 
@@ -405,7 +407,7 @@ public:
      * @return True if the view is properly initialized, false otherwise.
      * @return True if the view is properly initialized, false otherwise.
      */
      */
     [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
     [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
-        return std::get<0>(pools) != nullptr;
+        return view != nullptr;
     }
     }
 
 
     /**
     /**
@@ -414,7 +416,7 @@ public:
      * @return True if the view contains the given entity, false otherwise.
      * @return True if the view contains the given entity, false otherwise.
      */
      */
     [[nodiscard]] bool contains(const entity_type entt) const {
     [[nodiscard]] bool contains(const entity_type entt) const {
-        return (std::get<storage_type<Component> *>(pools)->contains(entt) && ...) && !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...);
+        return *this && (std::get<storage_type<Component> *>(pools)->contains(entt) && ...) && !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...);
     }
     }
 
 
     /**
     /**
@@ -579,6 +581,8 @@ class basic_view<Entity, exclude_t<>, Component> final {
         class iterable_view_iterator {
         class iterable_view_iterator {
             friend class iterable_view;
             friend class iterable_view;
 
 
+            iterable_view_iterator() ENTT_NOEXCEPT = default;
+
             template<typename... Discard>
             template<typename... Discard>
             iterable_view_iterator(It... from, Discard...) ENTT_NOEXCEPT
             iterable_view_iterator(It... from, Discard...) ENTT_NOEXCEPT
                 : it{from...}
                 : it{from...}
@@ -616,8 +620,8 @@ class basic_view<Entity, exclude_t<>, Component> final {
             std::tuple<It...> it;
             std::tuple<It...> it;
         };
         };
 
 
-        iterable_view(storage_type &ref)
-            : pool{&ref}
+        iterable_view(storage_type * const ref)
+            : pool{ref}
         {}
         {}
 
 
     public:
     public:
@@ -633,23 +637,23 @@ class basic_view<Entity, exclude_t<>, Component> final {
         >;
         >;
 
 
         [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
         [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-            return { pool->basic_sparse_set<entity_type>::begin(), pool->begin() };
+            return pool ? iterator{pool->basic_sparse_set<entity_type>::begin(), pool->begin()} : iterator{};
         }
         }
 
 
         [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
         [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
-            return { pool->basic_sparse_set<entity_type>::end(), pool->end() };
+            return pool ? iterator{pool->basic_sparse_set<entity_type>::end(), pool->end()} : iterator{};
         }
         }
 
 
         [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
         [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
-            return { pool->basic_sparse_set<entity_type>::rbegin(), pool->rbegin() };
+            return pool ? reverse_iterator{pool->basic_sparse_set<entity_type>::rbegin(), pool->rbegin()} : reverse_iterator{};
         }
         }
 
 
         [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
         [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
-            return { pool->basic_sparse_set<entity_type>::rend(), pool->rend() };
+            return pool ? reverse_iterator{pool->basic_sparse_set<entity_type>::rend(), pool->rend()} : reverse_iterator{};
         }
         }
 
 
     private:
     private:
-        storage_type *pool;
+        storage_type * const pool;
     };
     };
 
 
 public:
 public:
@@ -682,7 +686,7 @@ public:
      * @return Number of entities that have the given component.
      * @return Number of entities that have the given component.
      */
      */
     [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
     [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
-        return pool->size();
+        return *this ? pool->size() : size_type{};
     }
     }
 
 
     /**
     /**
@@ -690,7 +694,7 @@ public:
      * @return True if the view is empty, false otherwise.
      * @return True if the view is empty, false otherwise.
      */
      */
     [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
     [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
-        return pool->empty();
+        return !*this || pool->empty();
     }
     }
 
 
     /**
     /**
@@ -702,7 +706,7 @@ public:
      * @return A pointer to the array of components.
      * @return A pointer to the array of components.
      */
      */
     [[nodiscard]] raw_type * raw() const ENTT_NOEXCEPT {
     [[nodiscard]] raw_type * raw() const ENTT_NOEXCEPT {
-        return pool->raw();
+        return *this ? pool->raw() : nullptr;
     }
     }
 
 
     /**
     /**
@@ -714,7 +718,7 @@ public:
      * @return A pointer to the array of entities.
      * @return A pointer to the array of entities.
      */
      */
     [[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
     [[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
-        return pool->data();
+        return *this ? pool->data() : nullptr;
     }
     }
 
 
     /**
     /**
@@ -726,7 +730,7 @@ public:
      * @return An iterator to the first entity of the view.
      * @return An iterator to the first entity of the view.
      */
      */
     [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
     [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-        return pool->basic_sparse_set<entity_type>::begin();
+        return *this ? pool->basic_sparse_set<entity_type>::begin() : iterator{};
     }
     }
 
 
     /**
     /**
@@ -739,7 +743,7 @@ public:
      * @return An iterator to the entity following the last entity of the view.
      * @return An iterator to the entity following the last entity of the view.
      */
      */
     [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
     [[nodiscard]] iterator end() const ENTT_NOEXCEPT {
-        return pool->basic_sparse_set<entity_type>::end();
+        return *this ? pool->basic_sparse_set<entity_type>::end() : iterator{};
     }
     }
 
 
     /**
     /**
@@ -751,7 +755,7 @@ public:
      * @return An iterator to the first entity of the reversed view.
      * @return An iterator to the first entity of the reversed view.
      */
      */
     [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
     [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
-        return pool->basic_sparse_set<entity_type>::rbegin();
+        return *this ? pool->basic_sparse_set<entity_type>::rbegin() : reverse_iterator{};
     }
     }
 
 
     /**
     /**
@@ -766,7 +770,7 @@ public:
      * reversed view.
      * reversed view.
      */
      */
     [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
     [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
-        return pool->basic_sparse_set<entity_type>::rend();
+        return *this ? pool->basic_sparse_set<entity_type>::rend() : reverse_iterator{};
     }
     }
 
 
     /**
     /**
@@ -796,7 +800,7 @@ public:
      * iterator otherwise.
      * iterator otherwise.
      */
      */
     [[nodiscard]] iterator find(const entity_type entt) const {
     [[nodiscard]] iterator find(const entity_type entt) const {
-        const auto it = pool->find(entt);
+        const auto it = *this ? pool->find(entt) : end();
         return it != end() && *it == entt ? it : end();
         return it != end() && *it == entt ? it : end();
     }
     }
 
 
@@ -823,7 +827,7 @@ public:
      * @return True if the view contains the given entity, false otherwise.
      * @return True if the view contains the given entity, false otherwise.
      */
      */
     [[nodiscard]] bool contains(const entity_type entt) const {
     [[nodiscard]] bool contains(const entity_type entt) const {
-        return pool->contains(entt);
+        return *this && pool->contains(entt);
     }
     }
 
 
     /**
     /**
@@ -843,6 +847,8 @@ public:
      */
      */
     template<typename... Comp>
     template<typename... Comp>
     [[nodiscard]] decltype(auto) get(const entity_type entt) const {
     [[nodiscard]] decltype(auto) get(const entity_type entt) const {
+        ENTT_ASSERT(contains(entt));
+
         if constexpr(sizeof...(Comp) == 0) {
         if constexpr(sizeof...(Comp) == 0) {
             if constexpr(std::is_same_v<typename storage_type::storage_category, empty_storage_tag>) {
             if constexpr(std::is_same_v<typename storage_type::storage_category, empty_storage_tag>) {
                 return std::make_tuple();
                 return std::make_tuple();
@@ -881,7 +887,7 @@ public:
     void each(Func func) const {
     void each(Func func) const {
         if constexpr(std::is_same_v<typename storage_type::storage_category, empty_storage_tag>) {
         if constexpr(std::is_same_v<typename storage_type::storage_category, empty_storage_tag>) {
             if constexpr(std::is_invocable_v<Func>) {
             if constexpr(std::is_invocable_v<Func>) {
-                for(auto pos = pool->size(); pos; --pos) {
+                for(auto pos = size(); pos; --pos) {
                     func();
                     func();
                 }
                 }
             } else {
             } else {
@@ -889,15 +895,17 @@ public:
                     func(component);
                     func(component);
                 }
                 }
             }
             }
-        } else if constexpr(is_applicable_v<Func, decltype(*each().begin())>) {
-            auto raw = pool->begin();
-
-            for(const auto entt: *this) {
-                func(entt, *(raw++));
-            }
         } else {
         } else {
-            for(auto &&component: *pool) {
-                func(component);
+            if constexpr(is_applicable_v<Func, decltype(*each().begin())>) {
+                for(const auto pack: each()) {
+                    std::apply(func, pack);
+                }
+            } else {
+                if(*this) {
+                    for(auto &&component: *pool) {
+                        func(component);
+                    }
+                }
             }
             }
         }
         }
     }
     }
@@ -916,7 +924,7 @@ public:
      * @return An iterable object to use to _visit_ the view.
      * @return An iterable object to use to _visit_ the view.
      */
      */
     [[nodiscard]] iterable_view each() const ENTT_NOEXCEPT {
     [[nodiscard]] iterable_view each() const ENTT_NOEXCEPT {
-        return iterable_view{*pool};
+        return iterable_view{pool};
     }
     }
 
 
 private:
 private:

+ 66 - 0
test/entt/entity/group.cpp

@@ -77,6 +77,41 @@ TEST(NonOwningGroup, Functionalities) {
     ASSERT_FALSE(invalid);
     ASSERT_FALSE(invalid);
 }
 }
 
 
+TEST(NonOwningGroup, Invalid) {
+    entt::registry registry{};
+    auto group = std::as_const(registry).group(entt::get<const empty_type, const int>);
+
+    const auto entity = registry.create();
+    registry.emplace<empty_type>(entity);
+    registry.emplace<int>(entity);
+
+    ASSERT_FALSE(group);
+
+    ASSERT_TRUE(group.empty());
+    ASSERT_EQ(group.size(), 0u);
+    ASSERT_EQ(group.capacity(), 0u);
+    ASSERT_NO_THROW(group.shrink_to_fit());
+
+    ASSERT_EQ(group.data(), nullptr);
+
+    ASSERT_EQ(group.begin(), group.end());
+    ASSERT_EQ(group.rbegin(), group.rend());
+
+    ASSERT_FALSE(group.contains(entity));
+    ASSERT_EQ(group.find(entity), group.end());
+    ASSERT_EQ(group.front(), entt::entity{entt::null});
+    ASSERT_EQ(group.back(), entt::entity{entt::null});
+
+    group.each([](const auto, const auto &) { FAIL(); });
+    group.each([](const auto &) { FAIL(); });
+
+    for(auto [entity, value]: group.each()) { FAIL(); }
+    for(auto first = group.each().rbegin(), last = group.each().rend(); first != last; ++first) { FAIL(); }
+
+    ASSERT_NO_THROW(group.sort([](const auto, const auto) { FAIL(), true; }));
+    ASSERT_NO_THROW(group.sort<const empty_type>());
+}
+
 TEST(NonOwningGroup, ElementAccess) {
 TEST(NonOwningGroup, ElementAccess) {
     entt::registry registry;
     entt::registry registry;
     auto group = registry.group(entt::get<int, char>);
     auto group = registry.group(entt::get<int, char>);
@@ -615,6 +650,37 @@ TEST(OwningGroup, Functionalities) {
     ASSERT_FALSE(invalid);
     ASSERT_FALSE(invalid);
 }
 }
 
 
+TEST(OwningGroup, Invalid) {
+    entt::registry registry{};
+    auto group = std::as_const(registry).group<const int>(entt::get<const empty_type>);
+
+    const auto entity = registry.create();
+    registry.emplace<empty_type>(entity);
+    registry.emplace<int>(entity);
+
+    ASSERT_FALSE(group);
+
+    ASSERT_TRUE(group.empty());
+    ASSERT_EQ(group.size(), 0u);
+
+    ASSERT_EQ(group.raw<const int>(), nullptr);
+    ASSERT_EQ(group.data(), nullptr);
+
+    ASSERT_EQ(group.begin(), group.end());
+    ASSERT_EQ(group.rbegin(), group.rend());
+
+    ASSERT_FALSE(group.contains(entity));
+    ASSERT_EQ(group.find(entity), group.end());
+    ASSERT_EQ(group.front(), entt::entity{entt::null});
+    ASSERT_EQ(group.back(), entt::entity{entt::null});
+
+    group.each([](const auto, const auto &) { FAIL(); });
+    group.each([](const auto &) { FAIL(); });
+
+    for(auto [entity, value]: group.each()) { FAIL(); }
+    for(auto first = group.each().rbegin(), last = group.each().rend(); first != last; ++first) { FAIL(); }
+}
+
 TEST(OwningGroup, ElementAccess) {
 TEST(OwningGroup, ElementAccess) {
     entt::registry registry;
     entt::registry registry;
     auto group = registry.group<int>(entt::get<char>);
     auto group = registry.group<int>(entt::get<char>);

+ 5 - 5
test/entt/entity/registry.cpp

@@ -701,16 +701,16 @@ TEST(Registry, CleanViewAfterRemoveAndClear) {
     auto view = registry.view<int, char>();
     auto view = registry.view<int, char>();
 
 
     const auto entity = registry.create();
     const auto entity = registry.create();
-    registry.emplace<int>(entity, 0);
-    registry.emplace<char>(entity, 'c');
+    registry.emplace<int>(entity);
+    registry.emplace<char>(entity);
 
 
     ASSERT_EQ(view.size_hint(), 1u);
     ASSERT_EQ(view.size_hint(), 1u);
 
 
     registry.remove<char>(entity);
     registry.remove<char>(entity);
 
 
-    ASSERT_EQ(view.size_hint(), 0u);
+    ASSERT_EQ(view.size_hint(), 1u);
 
 
-    registry.emplace<char>(entity, 'c');
+    registry.emplace<char>(entity);
 
 
     ASSERT_EQ(view.size_hint(), 1u);
     ASSERT_EQ(view.size_hint(), 1u);
 
 
@@ -718,7 +718,7 @@ TEST(Registry, CleanViewAfterRemoveAndClear) {
 
 
     ASSERT_EQ(view.size_hint(), 0u);
     ASSERT_EQ(view.size_hint(), 0u);
 
 
-    registry.emplace<int>(entity, 0);
+    registry.emplace<int>(entity);
 
 
     ASSERT_EQ(view.size_hint(), 1u);
     ASSERT_EQ(view.size_hint(), 1u);
 
 

+ 65 - 0
test/entt/entity/view.cpp

@@ -63,6 +63,45 @@ TEST(SingleComponentView, Functionalities) {
     ASSERT_FALSE(invalid);
     ASSERT_FALSE(invalid);
 }
 }
 
 
+TEST(SingleComponentView, Invalid) {
+    entt::registry registry{};
+    auto eview = std::as_const(registry).view<const empty_type>();
+    auto cview = std::as_const(registry).view<const int>();
+
+    const auto entity = registry.create();
+    registry.emplace<empty_type>(entity);
+    registry.emplace<int>(entity);
+
+    ASSERT_FALSE(cview);
+    ASSERT_FALSE(eview);
+
+    ASSERT_TRUE(cview.empty());
+    ASSERT_EQ(eview.size(), 0u);
+
+    ASSERT_EQ(cview.raw(), nullptr);
+    ASSERT_EQ(eview.data(), nullptr);
+
+    ASSERT_EQ(cview.begin(), cview.end());
+    ASSERT_EQ(eview.rbegin(), eview.rend());
+
+    ASSERT_FALSE(cview.contains(entity));
+    ASSERT_EQ(eview.find(entity), eview.end());
+    ASSERT_EQ(cview.front(), entt::entity{entt::null});
+    ASSERT_EQ(eview.back(), entt::entity{entt::null});
+
+    cview.each([](const auto, const auto &) { FAIL(); });
+    cview.each([](const auto &) { FAIL(); });
+
+    eview.each([](const auto) { FAIL(); });
+    eview.each([]() { FAIL(); });
+
+    for(auto [entity, value]: cview.each()) { FAIL(); }
+    for(auto first = cview.each().rbegin(), last = cview.each().rend(); first != last; ++first) { FAIL(); }
+
+    for(const auto entity: eview.each()) { FAIL(); }
+    for(auto first = eview.each().rbegin(), last = eview.each().rend(); first != last; ++first) { FAIL(); }
+}
+
 TEST(SingleComponentView, ElementAccess) {
 TEST(SingleComponentView, ElementAccess) {
     entt::registry registry;
     entt::registry registry;
     auto view = registry.view<int>();
     auto view = registry.view<int>();
@@ -363,6 +402,32 @@ TEST(MultiComponentView, Functionalities) {
     ASSERT_FALSE(invalid);
     ASSERT_FALSE(invalid);
 }
 }
 
 
+TEST(MultiComponentView, Invalid) {
+    entt::registry registry{};
+    auto view = std::as_const(registry).view<const empty_type, const int>();
+
+    const auto entity = registry.create();
+    registry.emplace<empty_type>(entity);
+    registry.emplace<int>(entity);
+
+    ASSERT_FALSE(view);
+
+    ASSERT_EQ(view.size_hint(), 0u);
+
+    ASSERT_EQ(view.begin(), view.end());
+
+    ASSERT_FALSE(view.contains(entity));
+    ASSERT_EQ(view.find(entity), view.end());
+    ASSERT_EQ(view.front(), entt::entity{entt::null});
+    ASSERT_EQ(view.back(), entt::entity{entt::null});
+
+    view.each([](const auto, const auto &) { FAIL(); });
+    view.each([](const auto &) { FAIL(); });
+
+    for(auto [entity, value]: view.each()) { FAIL(); }
+    for(auto first = view.each().rbegin(), last = view.each().rend(); first != last; ++first) { FAIL(); }
+}
+
 TEST(MultiComponentView, Iterator) {
 TEST(MultiComponentView, Iterator) {
     entt::registry registry;
     entt::registry registry;
     const auto entity = registry.create();
     const auto entity = registry.create();