Browse Source

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

Michele Caini 5 years ago
parent
commit
60475d3354
7 changed files with 307 additions and 130 deletions
  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
 * deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning 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
+* 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.
 * 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

+ 36 - 7
docs/md/entity.md

@@ -36,12 +36,13 @@
 * [Views and Groups](#views-and-groups)
   * [Views](#views)
     * [View pack](#view-pack)
-  * [Runtime views](#runtime-views)
+    * [Runtime views](#runtime-views)
   * [Groups](#groups)
     * [Full-owning groups](#full-owning-groups)
     * [Partial-owning groups](#partial-owning-groups)
     * [Non-owning groups](#non-owning-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)
   * [Give me everything](#give-me-everything)
   * [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/>
 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
 `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
 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
 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/>
 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
 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:
@@ -1545,8 +1546,9 @@ list for owned components. It's also possible to ask a group if it contains a
 given entity.<br/>
 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
 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
@@ -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
 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
 
 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;
         };
 
-        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}
         {}
 
@@ -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>;
 
         [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-            return { handler->begin(), pools };
+            return handler ? iterator{handler->begin(), pools} : iterator{{}, pools};
         }
 
         [[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 {
-            return { handler->rbegin(), pools };
+            return handler ? reverse_iterator{handler->rbegin(), pools} : reverse_iterator{{}, pools};
         }
 
         [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
-            return { handler->rend(), pools };
+            return handler ? reverse_iterator{handler->rend(), pools} : reverse_iterator{{}, pools};
         }
 
     private:
-        basic_sparse_set<Entity> *handler;
+        basic_sparse_set<Entity> * const handler;
         const std::tuple<storage_type<Get> *...> pools;
     };
 
@@ -173,7 +173,7 @@ public:
      * @return Number of entities that have the given components.
      */
     [[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.
      */
     [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
-        return handler->capacity();
+        return *this ? handler->capacity() : size_type{};
     }
 
     /*! @brief Requests the removal of unused capacity. */
     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.
      */
     [[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.
      */
     [[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.
      */
     [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-        return handler->begin();
+        return *this ? handler->begin() : iterator{};
     }
 
     /**
@@ -233,7 +235,7 @@ public:
      * group.
      */
     [[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.
      */
     [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
-        return handler->rbegin();
+        return *this ? handler->rbegin() : reverse_iterator{};
     }
 
     /**
@@ -260,7 +262,7 @@ public:
      * reversed group.
      */
     [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
-        return handler->rend();
+        return *this ? handler->rend() : reverse_iterator{};
     }
 
     /**
@@ -290,7 +292,7 @@ public:
      * iterator otherwise.
      */
     [[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();
     }
 
@@ -317,7 +319,7 @@ public:
      * @return True if the group contains the given entity, false otherwise.
      */
     [[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>
     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({})))>) {
                 std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt)));
             } else {
@@ -395,7 +397,7 @@ public:
      * @return An iterable object to use to _visit_ the group.
      */
     [[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>
     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>
     void sort() const {
-        handler->respect(*std::get<storage_type<Component> *>(pools));
+        if(*this) {
+            handler->respect(*std::get<storage_type<Component> *>(pools));
+        }
     }
 
 private:
-    basic_sparse_set<entity_type> *handler;
+    basic_sparse_set<entity_type> * const handler;
     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;
         };
 
-        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},
-              length{&extent}
+              length{extent}
         {}
 
     public:
@@ -602,40 +608,40 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
         >;
 
         [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
-            return {
+            return length ? iterator{
                 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<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 {
-            return {
+            return length ? iterator{
                 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<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 {
-            return {
+            return length ? reverse_iterator{
                 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<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 {
-            return {
+            return length ? reverse_iterator{
                 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<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:
         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
@@ -663,7 +669,7 @@ public:
      * @return Number of entities that have the given components.
      */
     [[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.
      */
     [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
-        return !*length;
+        return !*this || !*length;
     }
 
     /**
@@ -690,7 +696,8 @@ public:
     template<typename Component>
     [[nodiscard]] Component * raw() const ENTT_NOEXCEPT {
         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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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();
     }
 
@@ -812,7 +819,7 @@ public:
      * @return True if the group contains the given entity, false otherwise.
      */
     [[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.
      */
     [[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.
      */
     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);
-
+        
         if constexpr(sizeof...(Component) == 0) {
             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)...);
@@ -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)...));
             }, std::move(algo), std::forward<Args>(args)...);
         }
-
+        
         [this](auto *head, auto *... other) {
             for(auto next = *length; next; --next) {
                 const auto pos = next - 1;
@@ -958,7 +965,7 @@ public:
 
 private:
     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>
     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>
     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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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();
     }
 
@@ -405,7 +407,7 @@ public:
      * @return True if the view is properly initialized, false otherwise.
      */
     [[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.
      */
     [[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 {
             friend class iterable_view;
 
+            iterable_view_iterator() ENTT_NOEXCEPT = default;
+
             template<typename... Discard>
             iterable_view_iterator(It... from, Discard...) ENTT_NOEXCEPT
                 : it{from...}
@@ -616,8 +620,8 @@ class basic_view<Entity, exclude_t<>, Component> final {
             std::tuple<It...> it;
         };
 
-        iterable_view(storage_type &ref)
-            : pool{&ref}
+        iterable_view(storage_type * const ref)
+            : pool{ref}
         {}
 
     public:
@@ -633,23 +637,23 @@ class basic_view<Entity, exclude_t<>, Component> final {
         >;
 
         [[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 {
-            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 {
-            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 {
-            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:
-        storage_type *pool;
+        storage_type * const pool;
     };
 
 public:
@@ -682,7 +686,7 @@ public:
      * @return Number of entities that have the given component.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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.
      */
     [[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();
     }
 
@@ -823,7 +827,7 @@ public:
      * @return True if the view contains the given entity, false otherwise.
      */
     [[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>
     [[nodiscard]] decltype(auto) get(const entity_type entt) const {
+        ENTT_ASSERT(contains(entt));
+
         if constexpr(sizeof...(Comp) == 0) {
             if constexpr(std::is_same_v<typename storage_type::storage_category, empty_storage_tag>) {
                 return std::make_tuple();
@@ -881,7 +887,7 @@ public:
     void each(Func func) const {
         if constexpr(std::is_same_v<typename storage_type::storage_category, empty_storage_tag>) {
             if constexpr(std::is_invocable_v<Func>) {
-                for(auto pos = pool->size(); pos; --pos) {
+                for(auto pos = size(); pos; --pos) {
                     func();
                 }
             } else {
@@ -889,15 +895,17 @@ public:
                     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 {
-            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.
      */
     [[nodiscard]] iterable_view each() const ENTT_NOEXCEPT {
-        return iterable_view{*pool};
+        return iterable_view{pool};
     }
 
 private:

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

@@ -77,6 +77,41 @@ TEST(NonOwningGroup, Functionalities) {
     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) {
     entt::registry registry;
     auto group = registry.group(entt::get<int, char>);
@@ -615,6 +650,37 @@ TEST(OwningGroup, Functionalities) {
     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) {
     entt::registry registry;
     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>();
 
     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);
 
     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);
 
@@ -718,7 +718,7 @@ TEST(Registry, CleanViewAfterRemoveAndClear) {
 
     ASSERT_EQ(view.size_hint(), 0u);
 
-    registry.emplace<int>(entity, 0);
+    registry.emplace<int>(entity);
 
     ASSERT_EQ(view.size_hint(), 1u);
 

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

@@ -63,6 +63,45 @@ TEST(SingleComponentView, Functionalities) {
     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) {
     entt::registry registry;
     auto view = registry.view<int>();
@@ -363,6 +402,32 @@ TEST(MultiComponentView, Functionalities) {
     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) {
     entt::registry registry;
     const auto entity = registry.create();