Explorar el Código

added utility functions to groups/views

Michele Caini hace 7 años
padre
commit
3b92481133
Se han modificado 6 ficheros con 548 adiciones y 313 borrados
  1. 5 2
      TODO
  2. 121 12
      src/entt/entity/group.hpp
  3. 3 2
      src/entt/entity/registry.hpp
  4. 67 3
      src/entt/entity/view.hpp
  5. 335 293
      test/entt/entity/group.cpp
  6. 17 1
      test/entt/entity/view.cpp

+ 5 - 2
TODO

@@ -8,7 +8,6 @@
 * meta: sort of meta view based on meta stuff to iterate entities, void * and meta info objects
 * allow for built-in parallel each if possible
 * tags revenge: if it's possible, reintroduce them but without a link to entities (see #169 for more details)
-* empty components model allows for shared components and prefabs unity-like
 * allow to replace std:: with custom implementations
 * allow to sort groups (::respect can already work with begin/end instead of a whole sparse set)
   -it would ease by far the group trick for hierarchies that requires otherwise more boilerplate
@@ -17,4 +16,8 @@
   - define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
   - from Tommaso on discord view<Health, Transform>().where<Health>([](h) {h > 5}).where<Transform>([](t) {t.inside(aabb)});
 * remove runtime views, welcome reflection and what about snapshot?
-* add size+raw on multi comp views and groups, size retuns the actual size of the underlying pool and not that of the view/group
+* empty components model allows for shared components and prefabs unity-like
+  - each with entity return the shared component multiple times, one per entity that refers to it
+  - each components only return actual component, so shared components are returned only once
+* doc: when types of components that belong to groups are iterated out of their group, can we still add instances without problems? document it if needed
+* add a sort of "fast each" for when users know they are not to add/remove components, it can use directly raw access and improve even further performance

+ 121 - 12
src/entt/entity/group.hpp

@@ -102,6 +102,16 @@ public:
     /*! @brief Input iterator type. */
     using iterator_type = typename sparse_set<Entity>::iterator_type;
 
+    /**
+     * @brief Returns the number of existing components of the given type.
+     * @tparam Component Type of component of which to return the size.
+     * @return Number of existing components of the given type.
+     */
+    template<typename Component>
+    size_type size() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Component> *>(pools)->size();
+    }
+
     /**
      * @brief Returns the number of entities that have the given components.
      * @return Number of entities that have the given components.
@@ -110,6 +120,17 @@ public:
         return handler->size();
     }
 
+    /**
+     * @brief Checks whether the pool of a given component is empty.
+     * @tparam Component Type of component in which one is interested.
+     * @return True if the pool of the given component is empty, false
+     * otherwise.
+     */
+    template<typename Component>
+    bool empty() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Component> *>(pools)->empty();
+    }
+
     /**
      * @brief Checks whether the group is empty.
      * @return True if the group is empty, false otherwise.
@@ -118,6 +139,49 @@ public:
         return handler->empty();
     }
 
+    /**
+     * @brief Direct access to the list of components of a given pool.
+     *
+     * The returned pointer is such that range
+     * `[raw<Component>(), raw<Component>() + size<Component>()]` is always a
+     * valid range, even if the container is empty.
+     *
+     * @note
+     * There are no guarantees on the order of the components. Use `begin` and
+     * `end` if you want to iterate the group in the expected order.
+     *
+     * @warning
+     * Empty components aren't explicitly instantiated. Only one instance of the
+     * given type is created. Therefore, this function always returns a pointer
+     * to that instance.
+     *
+     * @tparam Component Type of component in which one is interested.
+     * @return A pointer to the array of components.
+     */
+    template<typename Component>
+    Component * raw() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Component> *>(pools)->raw();
+    }
+
+    /**
+     * @brief Direct access to the list of entities of a given pool.
+     *
+     * The returned pointer is such that range
+     * `[data<Component>(), data<Component>() + size<Component>()]` is always a
+     * valid range, even if the container is empty.
+     *
+     * @note
+     * There are no guarantees on the order of the entities. Use `begin` and
+     * `end` if you want to iterate the group in the expected order.
+     *
+     * @tparam Component Type of component in which one is interested.
+     * @return A pointer to the array of entities.
+     */
+    template<typename Component>
+    const entity_type * data() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Component> *>(pools)->data();
+    }
+
     /**
      * @brief Direct access to the list of entities.
      *
@@ -223,7 +287,6 @@ public:
         assert(contains(entity));
 
         if constexpr(sizeof...(Component) == 1) {
-            static_assert(std::disjunction_v<std::is_same<Component..., Get>..., std::is_same<std::remove_const_t<Component>..., Get>...>);
             return (std::get<pool_type<Component> *>(pools)->get(entity), ...);
         } else {
             return std::tuple<Component &...>{get<Component>(entity)...};
@@ -355,6 +418,16 @@ public:
     /*! @brief Input iterator type. */
     using iterator_type = typename sparse_set<Entity>::iterator_type;
 
+    /**
+     * @brief Returns the number of existing components of the given type.
+     * @tparam Component Type of component of which to return the size.
+     * @return Number of existing components of the given type.
+     */
+    template<typename Component>
+    size_type size() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Component> *>(pools)->size();
+    }
+
     /**
      * @brief Returns the number of entities that have the given components.
      * @return Number of entities that have the given components.
@@ -363,6 +436,17 @@ public:
         return *length;
     }
 
+    /**
+     * @brief Checks whether the pool of a given component is empty.
+     * @tparam Component Type of component in which one is interested.
+     * @return True if the pool of the given component is empty, false
+     * otherwise.
+     */
+    template<typename Component>
+    bool empty() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Component> *>(pools)->empty();
+    }
+
     /**
      * @brief Checks whether the group is empty.
      * @return True if the group is empty, false otherwise.
@@ -372,27 +456,54 @@ public:
     }
 
     /**
-     * @brief Direct access to the list of components of an owned type.
+     * @brief Direct access to the list of components of a given pool.
      *
-     * The returned pointer is such that range `[raw(), raw() + size()]` is
-     * always a valid range, even if the container is empty.
+     * The returned pointer is such that range
+     * `[raw<Component>(), raw<Component>() + size<Component>()]` is always a
+     * valid range, even if the container is empty.<br/>
+     * Moreover, in case the group owns the given component, the range
+     * `[raw<Component>(), raw<Component>() + size()]` is such that it contains
+     * the instances that are part of the group itself.
      *
      * @note
-     * There are no guarantees on the order of the components. Use `each` if you
-     * want to iterate the group in the expected order.
+     * There are no guarantees on the order of the components. Use `begin` and
+     * `end` if you want to iterate the group in the expected order.
      *
      * @warning
-     * Empty components aren't explicitly instantiated. Therefore, this function
-     * always returns `nullptr` for them.
+     * Empty components aren't explicitly instantiated. Only one instance of the
+     * given type is created. Therefore, this function always returns a pointer
+     * to that instance.
      *
+     * @tparam Component Type of component in which one is interested.
      * @return A pointer to the array of components.
      */
     template<typename Component>
     Component * raw() const ENTT_NOEXCEPT {
-        static_assert(std::disjunction_v<std::is_same<Component, Owned>..., std::is_same<std::remove_const_t<Component>, Owned>...>);
         return std::get<pool_type<Component> *>(pools)->raw();
     }
 
+    /**
+     * @brief Direct access to the list of entities of a given pool.
+     *
+     * The returned pointer is such that range
+     * `[data<Component>(), data<Component>() + size<Component>()]` is always a
+     * valid range, even if the container is empty.<br/>
+     * Moreover, in case the group owns the given component, the range
+     * `[data<Component>(), data<Component>() + size()]` is such that it
+     * contains the entities that are part of the group itself.
+     *
+     * @note
+     * There are no guarantees on the order of the entities. Use `begin` and
+     * `end` if you want to iterate the group in the expected order.
+     *
+     * @tparam Component Type of component in which one is interested.
+     * @return A pointer to the array of entities.
+     */
+    template<typename Component>
+    const entity_type * data() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Component> *>(pools)->data();
+    }
+
     /**
      * @brief Direct access to the list of entities.
      *
@@ -401,7 +512,7 @@ public:
      *
      * @note
      * There are no guarantees on the order of the entities. Use `begin` and
-     * `end` if you want to iterate the view in the expected order.
+     * `end` if you want to iterate the group in the expected order.
      *
      * @return A pointer to the array of entities.
      */
@@ -498,8 +609,6 @@ public:
         assert(contains(entity));
 
         if constexpr(sizeof...(Component) == 1) {
-            static_assert(std::disjunction_v<std::is_same<Component..., Owned>..., std::is_same<std::remove_const_t<Component>..., Owned>...,
-                          std::is_same<Component..., Get>..., std::is_same<std::remove_const_t<Component>..., Get>...>);
             return (std::get<pool_type<Component> *>(pools)->get(entity), ...);
         } else {
             return std::tuple<Component &...>{get<Component>(entity)...};

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

@@ -382,8 +382,9 @@ public:
      * want to iterate entities and components in the expected order.
      *
      * @note
-     * Empty components aren't explicitly instantiated. Therefore, this function
-     * always returns `nullptr` for them.
+     * Empty components aren't explicitly instantiated. Only one instance of the
+     * given type is created. Therefore, this function always returns a pointer
+     * to that instance.
      *
      * @tparam Component Type of component in which one is interested.
      * @return A pointer to the array of components of the given type.

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

@@ -216,6 +216,16 @@ public:
     /*! @brief Input iterator type. */
     using iterator_type = iterator;
 
+    /**
+     * @brief Returns the number of existing components of the given type.
+     * @tparam Comp Type of component of which to return the size.
+     * @return Number of existing components of the given type.
+     */
+    template<typename Comp>
+    size_type size() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Comp> *>(pools)->size();
+    }
+
     /**
      * @brief Estimates the number of entities that have the given components.
      * @return Estimated number of entities that have the given components.
@@ -224,6 +234,17 @@ public:
         return std::min({ std::get<pool_type<Component> *>(pools)->size()... });
     }
 
+    /**
+     * @brief Checks whether the pool of a given component is empty.
+     * @tparam Comp Type of component in which one is interested.
+     * @return True if the pool of the given component is empty, false
+     * otherwise.
+     */
+    template<typename Comp>
+    bool empty() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Comp> *>(pools)->empty();
+    }
+
     /**
      * @brief Checks if the view is definitely empty.
      * @return True if the view is definitely empty, false otherwise.
@@ -232,6 +253,49 @@ public:
         return (std::get<pool_type<Component> *>(pools)->empty() || ...);
     }
 
+    /**
+     * @brief Direct access to the list of components of a given pool.
+     *
+     * The returned pointer is such that range
+     * `[raw<Comp>(), raw<Comp>() + size<Comp>()]` is always a valid range, even
+     * if the container is empty.
+     *
+     * @note
+     * There are no guarantees on the order of the components. Use `begin` and
+     * `end` if you want to iterate the view in the expected order.
+     *
+     * @warning
+     * Empty components aren't explicitly instantiated. Only one instance of the
+     * given type is created. Therefore, this function always returns a pointer
+     * to that instance.
+     *
+     * @tparam Comp Type of component in which one is interested.
+     * @return A pointer to the array of components.
+     */
+    template<typename Comp>
+    Comp * raw() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Comp> *>(pools)->raw();
+    }
+
+    /**
+     * @brief Direct access to the list of entities of a given pool.
+     *
+     * The returned pointer is such that range
+     * `[data<Comp>(), data<Comp>() + size<Comp>()]` is always a valid range,
+     * even if the container is empty.
+     *
+     * @note
+     * There are no guarantees on the order of the entities. Use `begin` and
+     * `end` if you want to iterate the view in the expected order.
+     *
+     * @tparam Comp Type of component in which one is interested.
+     * @return A pointer to the array of entities.
+     */
+    template<typename Comp>
+    const entity_type * data() const ENTT_NOEXCEPT {
+        return std::get<pool_type<Comp> *>(pools)->data();
+    }
+
     /**
      * @brief Returns an iterator to the first entity that has the given
      * components.
@@ -315,7 +379,6 @@ public:
         assert(contains(entity));
 
         if constexpr(sizeof...(Comp) == 1) {
-            static_assert(std::disjunction_v<std::is_same<Comp..., Component>..., std::is_same<std::remove_const_t<Comp>..., Component>...>);
             return (std::get<pool_type<Comp> *>(pools)->get(entity), ...);
         } else {
             return std::tuple<Comp &...>{get<Comp>(entity)...};
@@ -461,8 +524,9 @@ public:
      * `end` if you want to iterate the view in the expected order.
      *
      * @warning
-     * Empty components aren't explicitly instantiated. Therefore, this function
-     * always returns `nullptr` for them.
+     * Empty components aren't explicitly instantiated. Only one instance of the
+     * given type is created. Therefore, this function always returns a pointer
+     * to that instance.
      *
      * @return A pointer to the array of components.
      */

+ 335 - 293
test/entt/entity/group.cpp

@@ -11,6 +11,8 @@ TEST(NonOwningGroup, Functionalities) {
     auto cgroup = std::as_const(registry).group<>(entt::get<const int, const char>);
 
     ASSERT_TRUE(group.empty());
+    ASSERT_TRUE(group.empty<int>());
+    ASSERT_TRUE(cgroup.empty<const char>());
 
     const auto e0 = registry.create();
     registry.assign<char>(e0);
@@ -20,20 +22,28 @@ TEST(NonOwningGroup, Functionalities) {
     registry.assign<char>(e1);
 
     ASSERT_FALSE(group.empty());
+    ASSERT_FALSE(group.empty<int>());
+    ASSERT_FALSE(cgroup.empty<const char>());
     ASSERT_NO_THROW((group.begin()++));
     ASSERT_NO_THROW((++cgroup.begin()));
 
     ASSERT_NE(group.begin(), group.end());
     ASSERT_NE(cgroup.begin(), cgroup.end());
     ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
 
     registry.assign<int>(e0);
 
     ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
+    ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{2});
+    ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
 
     registry.remove<int>(e0);
 
     ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
 
     registry.get<char>(e0) = '1';
     registry.get<char>(e1) = '2';
@@ -47,58 +57,13 @@ TEST(NonOwningGroup, Functionalities) {
 
     ASSERT_EQ(*(group.data() + 0), e1);
 
-    registry.remove<char>(e0);
-    registry.remove<char>(e1);
-
-    ASSERT_EQ(group.begin(), group.end());
-    ASSERT_EQ(cgroup.begin(), cgroup.end());
-    ASSERT_TRUE(group.empty());
-}
-
-TEST(OwningGroup, Functionalities) {
-    entt::registry registry;
-    auto group = registry.group<int>(entt::get<char>);
-    auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
-
-    ASSERT_TRUE(group.empty());
-
-    const auto e0 = registry.create();
-    registry.assign<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
-
-    ASSERT_FALSE(group.empty());
-    ASSERT_NO_THROW((group.begin()++));
-    ASSERT_NO_THROW((++cgroup.begin()));
-
-    ASSERT_NE(group.begin(), group.end());
-    ASSERT_NE(cgroup.begin(), cgroup.end());
-    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
-
-    registry.assign<int>(e0);
-
-    ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
-
-    registry.remove<int>(e0);
-
-    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
-
-    registry.get<char>(e0) = '1';
-    registry.get<char>(e1) = '2';
-    registry.get<int>(e1) = 42;
+    ASSERT_EQ(*(group.data<int>() + 0), e1);
+    ASSERT_EQ(*(group.data<char>() + 0), e0);
+    ASSERT_EQ(*(cgroup.data<const char>() + 1), e1);
 
-    ASSERT_EQ(*(cgroup.raw<const int>() + 0), 42);
     ASSERT_EQ(*(group.raw<int>() + 0), 42);
-
-    for(auto entity: group) {
-        ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
-        ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
-        ASSERT_EQ(cgroup.get<const char>(entity), '2');
-    }
-
-    ASSERT_EQ(*(group.data() + 0), e1);
+    ASSERT_EQ(*(group.raw<char>() + 0), '1');
+    ASSERT_EQ(*(cgroup.raw<const char>() + 1), '2');
 
     registry.remove<char>(e0);
     registry.remove<char>(e1);
@@ -127,25 +92,6 @@ TEST(NonOwningGroup, ElementAccess) {
     }
 }
 
-TEST(OwningGroup, ElementAccess) {
-    entt::registry registry;
-    auto group = registry.group<int>(entt::get<char>);
-    auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
-
-    const auto e0 = registry.create();
-    registry.assign<int>(e0);
-    registry.assign<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
-
-    for(typename decltype(group)::size_type i{}; i < group.size(); ++i) {
-        ASSERT_EQ(group[i], i ? e0 : e1);
-        ASSERT_EQ(cgroup[i], i ? e0 : e1);
-    }
-}
-
 TEST(NonOwningGroup, Contains) {
     entt::registry registry;
     auto group = registry.group<>(entt::get<int, char>);
@@ -164,24 +110,6 @@ TEST(NonOwningGroup, Contains) {
     ASSERT_TRUE(group.contains(e1));
 }
 
-TEST(OwningGroup, Contains) {
-    entt::registry registry;
-    auto group = registry.group<int>(entt::get<char>);
-
-    const auto e0 = registry.create();
-    registry.assign<int>(e0);
-    registry.assign<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
-
-    registry.destroy(e0);
-
-    ASSERT_FALSE(group.contains(e0));
-    ASSERT_TRUE(group.contains(e1));
-}
-
 TEST(NonOwningGroup, Empty) {
     entt::registry registry;
 
@@ -205,29 +133,6 @@ TEST(NonOwningGroup, Empty) {
     }
 }
 
-TEST(OwningGroup, Empty) {
-    entt::registry registry;
-
-    const auto e0 = registry.create();
-    registry.assign<double>(e0);
-    registry.assign<int>(e0);
-    registry.assign<float>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<char>(e1);
-    registry.assign<float>(e1);
-
-    for(auto entity: registry.group<char, int>(entt::get<float>)) {
-        (void)entity;
-        FAIL();
-    }
-
-    for(auto entity: registry.group<double, float>(entt::get<char, int>)) {
-        (void)entity;
-        FAIL();
-    }
-}
-
 TEST(NonOwningGroup, Each) {
     entt::registry registry;
     auto group = registry.group<>(entt::get<int, char>);
@@ -254,32 +159,6 @@ TEST(NonOwningGroup, Each) {
     ASSERT_EQ(cnt, std::size_t{0});
 }
 
-TEST(OwningGroup, Each) {
-    entt::registry registry;
-    auto group = registry.group<int>(entt::get<char>);
-
-    const auto e0 = registry.create();
-    registry.assign<int>(e0);
-    registry.assign<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
-
-    auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
-    std::size_t cnt = 0;
-
-    group.each([&cnt](auto, int &, char &) { ++cnt; });
-    group.each([&cnt](int &, char &) { ++cnt; });
-
-    ASSERT_EQ(cnt, std::size_t{4});
-
-    cgroup.each([&cnt](auto, const int &, const char &) { --cnt; });
-    cgroup.each([&cnt](const int &, const char &) { --cnt; });
-
-    ASSERT_EQ(cnt, std::size_t{0});
-}
-
 TEST(NonOwningGroup, Sort) {
     entt::registry registry;
     auto group = registry.group<>(entt::get<const int, unsigned int>);
@@ -341,34 +220,6 @@ TEST(NonOwningGroup, IndexRebuiltOnDestroy) {
     });
 }
 
-TEST(OwningGroup, IndexRebuiltOnDestroy) {
-    entt::registry registry;
-    auto group = registry.group<int>(entt::get<unsigned int>);
-
-    const auto e0 = registry.create();
-    const auto e1 = registry.create();
-
-    registry.assign<unsigned int>(e0, 0u);
-    registry.assign<unsigned int>(e1, 1u);
-
-    registry.assign<int>(e0, 0);
-    registry.assign<int>(e1, 1);
-
-    registry.destroy(e0);
-    registry.assign<int>(registry.create(), 42);
-
-    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
-    ASSERT_EQ(group[{}], e1);
-    ASSERT_EQ(group.get<int>(e1), 1);
-    ASSERT_EQ(group.get<unsigned int>(e1), 1u);
-
-    group.each([e1](auto entity, auto ivalue, auto uivalue) {
-        ASSERT_EQ(entity, e1);
-        ASSERT_EQ(ivalue, 1);
-        ASSERT_EQ(uivalue, 1u);
-    });
-}
-
 TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
     entt::registry registry;
     auto group = registry.group<>(entt::get<int, const char>);
@@ -386,6 +237,8 @@ TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
     ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>(0)), const char &>));
     ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char>(0)), std::tuple<int &, const char &>>));
     ASSERT_TRUE((std::is_same_v<decltype(group.get<const int, const char>(0)), std::tuple<const int &, const char &>>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.raw<const char>()), const char *>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.raw<int>()), int *>));
 
     group.each([](auto, auto &&i, auto &&c) {
         ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
@@ -393,37 +246,6 @@ TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
     });
 }
 
-TEST(OwningGroup, ConstNonConstAndAllInBetween) {
-    entt::registry registry;
-    auto group = registry.group<int, const char>(entt::get<double, const float>);
-
-    ASSERT_EQ(group.size(), decltype(group.size()){0});
-
-    const auto entity = registry.create();
-    registry.assign<int>(entity, 0);
-    registry.assign<char>(entity, 'c');
-    registry.assign<double>(entity, 0.);
-    registry.assign<float>(entity, 0.f);
-
-    ASSERT_EQ(group.size(), decltype(group.size()){1});
-
-    ASSERT_TRUE((std::is_same_v<decltype(group.get<int>(0)), int &>));
-    ASSERT_TRUE((std::is_same_v<decltype(group.get<const int>(0)), const int &>));
-    ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>(0)), const char &>));
-    ASSERT_TRUE((std::is_same_v<decltype(group.get<double>(0)), double &>));
-    ASSERT_TRUE((std::is_same_v<decltype(group.get<const double>(0)), const double &>));
-    ASSERT_TRUE((std::is_same_v<decltype(group.get<const float>(0)), const float &>));
-    ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char, double, const float>(0)), std::tuple<int &, const char &, double &, const float &>>));
-    ASSERT_TRUE((std::is_same_v<decltype(group.get<const int, const char, const double, const float>(0)), std::tuple<const int &, const char &, const double &, const float &>>));
-
-    group.each([](auto, auto &&i, auto &&c, auto &&d, auto &&f) {
-        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
-        ASSERT_TRUE((std::is_same_v<decltype(c), const char &>));
-        ASSERT_TRUE((std::is_same_v<decltype(d), double &>));
-        ASSERT_TRUE((std::is_same_v<decltype(f), const float &>));
-    });
-}
-
 TEST(NonOwningGroup, Find) {
     entt::registry registry;
     auto group = registry.group<>(entt::get<int, const char>);
@@ -469,51 +291,6 @@ TEST(NonOwningGroup, Find) {
     ASSERT_EQ(group.find(e4), group.end());
 }
 
-TEST(OwningGroup, Find) {
-    entt::registry registry;
-    auto group = registry.group<int>(entt::get<const char>);
-
-    const auto e0 = registry.create();
-    registry.assign<int>(e0);
-    registry.assign<char>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
-
-    const auto e2 = registry.create();
-    registry.assign<int>(e2);
-    registry.assign<char>(e2);
-
-    const auto e3 = registry.create();
-    registry.assign<int>(e3);
-    registry.assign<char>(e3);
-
-    registry.remove<int>(e1);
-
-    ASSERT_NE(group.find(e0), group.end());
-    ASSERT_EQ(group.find(e1), group.end());
-    ASSERT_NE(group.find(e2), group.end());
-    ASSERT_NE(group.find(e3), group.end());
-
-    auto it = group.find(e2);
-
-    ASSERT_EQ(*it, e2);
-    ASSERT_EQ(*(++it), e3);
-    ASSERT_EQ(*(++it), e0);
-    ASSERT_EQ(++it, group.end());
-    ASSERT_EQ(++group.find(e0), group.end());
-
-    const auto e4 = registry.create();
-    registry.destroy(e4);
-    const auto e5 = registry.create();
-    registry.assign<int>(e5);
-    registry.assign<char>(e5);
-
-    ASSERT_NE(group.find(e5), group.end());
-    ASSERT_EQ(group.find(e4), group.end());
-}
-
 TEST(NonOwningGroup, ExcludedComponents) {
     entt::registry registry;
 
@@ -562,20 +339,330 @@ TEST(NonOwningGroup, ExcludedComponents) {
     }
 }
 
-TEST(OwningGroup, ExcludedComponents) {
+TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
+    struct empty_type {};
     entt::registry registry;
+    const auto group = registry.group<>(entt::get<int, empty_type>);
 
     const auto e0 = registry.create();
-    registry.assign<int>(e0, 0);
+    registry.assign<empty_type>(e0);
+    registry.assign<int>(e0);
 
     const auto e1 = registry.create();
-    registry.assign<int>(e1, 1);
-    registry.assign<char>(e1);
-
-    const auto group = registry.group<int>(entt::exclude<char>);
+    registry.assign<empty_type>(e1);
+    registry.assign<int>(e1);
 
-    const auto e2 = registry.create();
-    registry.assign<int>(e2, 2);
+    registry.assign<int>(registry.create());
+
+    for(const auto entity: group) {
+        ASSERT_TRUE(entity == e0 || entity == e1);
+    }
+
+    group.each([e0, e1](const auto entity, const int &, const empty_type &) {
+        ASSERT_TRUE(entity == e0 || entity == e1);
+    });
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
+    ASSERT_EQ(&group.get<empty_type>(e0), &group.get<empty_type>(e1));
+}
+
+TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) {
+    entt::registry registry;
+    const auto group = registry.group<>(entt::get<int>, entt::exclude<char>);
+    const auto cgroup = std::as_const(registry).group<>(entt::get<const int>, entt::exclude<char>);
+
+    const auto entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
+
+    ASSERT_TRUE(group.empty());
+    ASSERT_TRUE(cgroup.empty());
+
+    registry.remove<char>(entity);
+
+    ASSERT_FALSE(group.empty());
+    ASSERT_FALSE(cgroup.empty());
+}
+
+TEST(OwningGroup, Functionalities) {
+    entt::registry registry;
+    auto group = registry.group<int>(entt::get<char>);
+    auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
+
+    ASSERT_TRUE(group.empty());
+    ASSERT_TRUE(group.empty<int>());
+    ASSERT_TRUE(cgroup.empty<const char>());
+
+    const auto e0 = registry.create();
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    ASSERT_FALSE(group.empty());
+    ASSERT_FALSE(group.empty<int>());
+    ASSERT_FALSE(cgroup.empty<const char>());
+    ASSERT_NO_THROW((group.begin()++));
+    ASSERT_NO_THROW((++cgroup.begin()));
+
+    ASSERT_NE(group.begin(), group.end());
+    ASSERT_NE(cgroup.begin(), cgroup.end());
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
+
+    registry.assign<int>(e0);
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
+    ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{2});
+    ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
+
+    registry.remove<int>(e0);
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
+
+    registry.get<char>(e0) = '1';
+    registry.get<char>(e1) = '2';
+    registry.get<int>(e1) = 42;
+
+    ASSERT_EQ(*(cgroup.raw<const int>() + 0), 42);
+    ASSERT_EQ(*(group.raw<int>() + 0), 42);
+
+    for(auto entity: group) {
+        ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
+        ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
+        ASSERT_EQ(cgroup.get<const char>(entity), '2');
+    }
+
+    ASSERT_EQ(*(group.data() + 0), e1);
+
+    ASSERT_EQ(*(group.data<int>() + 0), e1);
+    ASSERT_EQ(*(group.data<char>() + 0), e0);
+    ASSERT_EQ(*(cgroup.data<const char>() + 1), e1);
+
+    ASSERT_EQ(*(group.raw<int>() + 0), 42);
+    ASSERT_EQ(*(group.raw<char>() + 0), '1');
+    ASSERT_EQ(*(cgroup.raw<const char>() + 1), '2');
+
+    registry.remove<char>(e0);
+    registry.remove<char>(e1);
+
+    ASSERT_EQ(group.begin(), group.end());
+    ASSERT_EQ(cgroup.begin(), cgroup.end());
+    ASSERT_TRUE(group.empty());
+}
+
+TEST(OwningGroup, ElementAccess) {
+    entt::registry registry;
+    auto group = registry.group<int>(entt::get<char>);
+    auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    for(typename decltype(group)::size_type i{}; i < group.size(); ++i) {
+        ASSERT_EQ(group[i], i ? e0 : e1);
+        ASSERT_EQ(cgroup[i], i ? e0 : e1);
+    }
+}
+
+TEST(OwningGroup, Contains) {
+    entt::registry registry;
+    auto group = registry.group<int>(entt::get<char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    registry.destroy(e0);
+
+    ASSERT_FALSE(group.contains(e0));
+    ASSERT_TRUE(group.contains(e1));
+}
+
+TEST(OwningGroup, Empty) {
+    entt::registry registry;
+
+    const auto e0 = registry.create();
+    registry.assign<double>(e0);
+    registry.assign<int>(e0);
+    registry.assign<float>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<char>(e1);
+    registry.assign<float>(e1);
+
+    for(auto entity: registry.group<char, int>(entt::get<float>)) {
+        (void)entity;
+        FAIL();
+    }
+
+    for(auto entity: registry.group<double, float>(entt::get<char, int>)) {
+        (void)entity;
+        FAIL();
+    }
+}
+
+TEST(OwningGroup, Each) {
+    entt::registry registry;
+    auto group = registry.group<int>(entt::get<char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
+    std::size_t cnt = 0;
+
+    group.each([&cnt](auto, int &, char &) { ++cnt; });
+    group.each([&cnt](int &, char &) { ++cnt; });
+
+    ASSERT_EQ(cnt, std::size_t{4});
+
+    cgroup.each([&cnt](auto, const int &, const char &) { --cnt; });
+    cgroup.each([&cnt](const int &, const char &) { --cnt; });
+
+    ASSERT_EQ(cnt, std::size_t{0});
+}
+
+TEST(OwningGroup, IndexRebuiltOnDestroy) {
+    entt::registry registry;
+    auto group = registry.group<int>(entt::get<unsigned int>);
+
+    const auto e0 = registry.create();
+    const auto e1 = registry.create();
+
+    registry.assign<unsigned int>(e0, 0u);
+    registry.assign<unsigned int>(e1, 1u);
+
+    registry.assign<int>(e0, 0);
+    registry.assign<int>(e1, 1);
+
+    registry.destroy(e0);
+    registry.assign<int>(registry.create(), 42);
+
+    ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
+    ASSERT_EQ(group[{}], e1);
+    ASSERT_EQ(group.get<int>(e1), 1);
+    ASSERT_EQ(group.get<unsigned int>(e1), 1u);
+
+    group.each([e1](auto entity, auto ivalue, auto uivalue) {
+        ASSERT_EQ(entity, e1);
+        ASSERT_EQ(ivalue, 1);
+        ASSERT_EQ(uivalue, 1u);
+    });
+}
+
+TEST(OwningGroup, ConstNonConstAndAllInBetween) {
+    entt::registry registry;
+    auto group = registry.group<int, const char>(entt::get<double, const float>);
+
+    ASSERT_EQ(group.size(), decltype(group.size()){0});
+
+    const auto entity = registry.create();
+    registry.assign<int>(entity, 0);
+    registry.assign<char>(entity, 'c');
+    registry.assign<double>(entity, 0.);
+    registry.assign<float>(entity, 0.f);
+
+    ASSERT_EQ(group.size(), decltype(group.size()){1});
+
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<int>(0)), int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const int>(0)), const int &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>(0)), const char &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<double>(0)), double &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const double>(0)), const double &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const float>(0)), const float &>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char, double, const float>(0)), std::tuple<int &, const char &, double &, const float &>>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.get<const int, const char, const double, const float>(0)), std::tuple<const int &, const char &, const double &, const float &>>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.raw<const float>()), const float *>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.raw<double>()), double *>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.raw<const char>()), const char *>));
+    ASSERT_TRUE((std::is_same_v<decltype(group.raw<int>()), int *>));
+
+    group.each([](auto, auto &&i, auto &&c, auto &&d, auto &&f) {
+        ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
+        ASSERT_TRUE((std::is_same_v<decltype(c), const char &>));
+        ASSERT_TRUE((std::is_same_v<decltype(d), double &>));
+        ASSERT_TRUE((std::is_same_v<decltype(f), const float &>));
+    });
+}
+
+TEST(OwningGroup, Find) {
+    entt::registry registry;
+    auto group = registry.group<int>(entt::get<const char>);
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2);
+    registry.assign<char>(e2);
+
+    const auto e3 = registry.create();
+    registry.assign<int>(e3);
+    registry.assign<char>(e3);
+
+    registry.remove<int>(e1);
+
+    ASSERT_NE(group.find(e0), group.end());
+    ASSERT_EQ(group.find(e1), group.end());
+    ASSERT_NE(group.find(e2), group.end());
+    ASSERT_NE(group.find(e3), group.end());
+
+    auto it = group.find(e2);
+
+    ASSERT_EQ(*it, e2);
+    ASSERT_EQ(*(++it), e3);
+    ASSERT_EQ(*(++it), e0);
+    ASSERT_EQ(++it, group.end());
+    ASSERT_EQ(++group.find(e0), group.end());
+
+    const auto e4 = registry.create();
+    registry.destroy(e4);
+    const auto e5 = registry.create();
+    registry.assign<int>(e5);
+    registry.assign<char>(e5);
+
+    ASSERT_NE(group.find(e5), group.end());
+    ASSERT_EQ(group.find(e4), group.end());
+}
+
+TEST(OwningGroup, ExcludedComponents) {
+    entt::registry registry;
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 1);
+    registry.assign<char>(e1);
+
+    const auto group = registry.group<int>(entt::exclude<char>);
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2, 2);
 
     const auto e3 = registry.create();
     registry.assign<int>(e3, 3);
@@ -610,33 +697,6 @@ TEST(OwningGroup, ExcludedComponents) {
     }
 }
 
-TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
-    struct empty_type {};
-    entt::registry registry;
-    const auto group = registry.group<>(entt::get<int, empty_type>);
-
-    const auto e0 = registry.create();
-    registry.assign<empty_type>(e0);
-    registry.assign<int>(e0);
-
-    const auto e1 = registry.create();
-    registry.assign<empty_type>(e1);
-    registry.assign<int>(e1);
-
-    registry.assign<int>(registry.create());
-
-    for(const auto entity: group) {
-        ASSERT_TRUE(entity == e0 || entity == e1);
-    }
-
-    group.each([e0, e1](const auto entity, const int &, const empty_type &) {
-        ASSERT_TRUE(entity == e0 || entity == e1);
-    });
-
-    ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
-    ASSERT_EQ(&group.get<empty_type>(e0), &group.get<empty_type>(e1));
-}
-
 TEST(OwningGroup, EmptyAndNonEmptyTypes) {
     struct empty_type {};
     entt::registry registry;
@@ -664,24 +724,6 @@ TEST(OwningGroup, EmptyAndNonEmptyTypes) {
     ASSERT_EQ(&group.get<empty_type>(e0), &group.get<empty_type>(e1));
 }
 
-TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) {
-    entt::registry registry;
-    const auto group = registry.group<>(entt::get<int>, entt::exclude<char>);
-    const auto cgroup = std::as_const(registry).group<>(entt::get<const int>, entt::exclude<char>);
-
-    const auto entity = registry.create();
-    registry.assign<int>(entity);
-    registry.assign<char>(entity);
-
-    ASSERT_TRUE(group.empty());
-    ASSERT_TRUE(cgroup.empty());
-
-    registry.remove<char>(entity);
-
-    ASSERT_FALSE(group.empty());
-    ASSERT_FALSE(cgroup.empty());
-}
-
 TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {
     entt::registry registry;
     const auto group = registry.group<int>(entt::exclude<char>);

+ 17 - 1
test/entt/entity/view.cpp

@@ -41,7 +41,7 @@ TEST(SingleComponentView, Functionalities) {
     ASSERT_EQ(*(view.data() + 1), e0);
 
     ASSERT_EQ(*(view.raw() + 0), '2');
-    ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
+    ASSERT_EQ(*(cview.raw() + 1), '1');
 
     registry.remove<char>(e0);
     registry.remove<char>(e1);
@@ -200,6 +200,8 @@ TEST(MultipleComponentView, Functionalities) {
     auto cview = std::as_const(registry).view<const int, const char>();
 
     ASSERT_TRUE(view.empty());
+    ASSERT_TRUE(view.empty<int>());
+    ASSERT_TRUE(cview.empty<const char>());
 
     const auto e0 = registry.create();
     registry.assign<char>(e0);
@@ -208,6 +210,8 @@ TEST(MultipleComponentView, Functionalities) {
     registry.assign<int>(e1);
 
     ASSERT_FALSE(view.empty());
+    ASSERT_FALSE(view.empty<int>());
+    ASSERT_FALSE(cview.empty<const char>());
 
     registry.assign<char>(e1);
 
@@ -222,6 +226,8 @@ TEST(MultipleComponentView, Functionalities) {
     ASSERT_NE(view.begin(), view.end());
     ASSERT_NE(cview.begin(), cview.end());
     ASSERT_EQ(view.size(), decltype(view.size()){1});
+    ASSERT_EQ(view.size<int>(), decltype(view.size()){1});
+    ASSERT_EQ(cview.size<const char>(), decltype(view.size()){2});
 
     registry.get<char>(e0) = '1';
     registry.get<char>(e1) = '2';
@@ -232,6 +238,14 @@ TEST(MultipleComponentView, Functionalities) {
         ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
         ASSERT_EQ(cview.get<const char>(entity), '2');
     }
+
+    ASSERT_EQ(*(view.data<int>() + 0), e1);
+    ASSERT_EQ(*(view.data<char>() + 0), e0);
+    ASSERT_EQ(*(cview.data<const char>() + 1), e1);
+
+    ASSERT_EQ(*(view.raw<int>() + 0), 42);
+    ASSERT_EQ(*(view.raw<char>() + 0), '1');
+    ASSERT_EQ(*(cview.raw<const char>() + 1), '2');
 }
 
 TEST(MultipleComponentView, Iterator) {
@@ -389,6 +403,8 @@ TEST(MultipleComponentView, ConstNonConstAndAllInBetween) {
     ASSERT_TRUE((std::is_same_v<decltype(view.get<const char>(0)), const char &>));
     ASSERT_TRUE((std::is_same_v<decltype(view.get<int, const char>(0)), std::tuple<int &, const char &>>));
     ASSERT_TRUE((std::is_same_v<decltype(view.get<const int, const char>(0)), std::tuple<const int &, const char &>>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.raw<const char>()), const char *>));
+    ASSERT_TRUE((std::is_same_v<decltype(view.raw<int>()), int *>));
 
     view.each([](auto, auto &&i, auto &&c) {
         ASSERT_TRUE((std::is_same_v<decltype(i), int &>));