ソースを参照

storage/view/group: reintroduce (opaque) ::raw functions as it ought to be

Michele Caini 4 年 前
コミット
b062bbf58d

+ 26 - 10
docs/md/entity.md

@@ -10,6 +10,7 @@
   * [Type-less and bitset-free](#type-less-and-bitset-free)
   * [Type-less and bitset-free](#type-less-and-bitset-free)
   * [Build your own](#build-your-own)
   * [Build your own](#build-your-own)
   * [Pay per use](#pay-per-use)
   * [Pay per use](#pay-per-use)
+  * [All or nothing](#all-or-nothing)
 * [Vademecum](#vademecum)
 * [Vademecum](#vademecum)
 * [Pools](#pools)
 * [Pools](#pools)
 * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
 * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
@@ -118,6 +119,19 @@ performance where needed.
 So far, this choice has proven to be a good one and I really hope it can be for
 So far, this choice has proven to be a good one and I really hope it can be for
 many others besides me.
 many others besides me.
 
 
+## All or nothing
+
+`EnTT` is such that a `T**` pointer (or whatever a custom pool returns) is
+always available to directly access all the instances of a given component type
+`T`.<br/>
+I cannot say whether it will be useful or not to the reader, but it's worth to
+mention it since it's one of the corner stones of this library.
+
+Many of the tools described below give the possibility to get this information
+and have been designed around this need.<br/>
+The rest is experimentation and the desire to invent something new, hoping to
+have succeeded.
+
 # Vademecum
 # Vademecum
 
 
 The registry to store, the views and the groups to iterate. That's all.
 The registry to store, the views and the groups to iterate. That's all.
@@ -1237,11 +1251,12 @@ different in the two cases.
 Single component views are specialized in order to give a boost in terms of
 Single component views are specialized in order to give a boost in terms of
 performance in all the situations. This kind of views can access the underlying
 performance in all the situations. This kind of views can access the underlying
 data structures directly and avoid superfluous checks. There is nothing as fast
 data structures directly and avoid superfluous checks. There is nothing as fast
-as a single component view. In fact, they walk through a packed array of
-components and return them one at a time.<br/>
-Single component views offer a bunch of functionalities to get the number of
-entities they are going to return and a raw access to the entity list. It's also
-possible to ask a view if it contains a given entity.<br/>
+as a single component view. In fact, they walk through a packed (actually paged)
+array of components and return them one at a time.<br/>
+Single component views also offer a bunch of functionalities to get the number
+of entities they are going to return and a raw access to the entity list as well
+as to the component list. It's also possible to ask a 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.
 
 
 Multi component views iterate entities that have at least all the given
 Multi component views iterate entities that have at least all the given
@@ -1435,8 +1450,9 @@ However, it's unlikely that users will be able to appreciate the impact of
 groups on the other functionalities of a registry.
 groups on the other functionalities of a registry.
 
 
 Groups offer a bunch of functionalities to get the number of entities they are
 Groups offer a bunch of functionalities to get the number of entities they are
-going to return and a raw access to the entity list. It's also possible to ask a
-group if it contains a given entity.<br/>
+going to return and a raw access to the entity list as well as to the component
+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.
 Refer to the inline documentation for all the details.
 
 
 There is no need to store groups aside for they are extremely cheap to
 There is no need to store groups aside for they are extremely cheap to
@@ -1846,9 +1862,9 @@ When an empty type is detected, it's not instantiated in any case. Therefore,
 only the entities to which it's assigned are made available.<br/>
 only the entities to which it's assigned are made available.<br/>
 There doesn't exist a way to _get_ empty types from a registry, views and groups
 There doesn't exist a way to _get_ empty types from a registry, views and groups
 will never return instances for them (for example, during a call to `each`) and
 will never return instances for them (for example, during a call to `each`) and
-some functions such as `try_get` aren't available for empty types. Finally, the
-`sort` functionality will only accepts callbacks that require to return entities
-rather than components:
+some functions such as `try_get` or the raw access to the list of components
+aren't available for empty types. Finally, the `sort` functionality will only
+accepts callbacks that require to return entities rather than components:
 
 
 ```cpp
 ```cpp
 registry.sort<empty_type>([](const entt::entity lhs, const entt::entity rhs) {
 registry.sort<empty_type>([](const entt::entity lhs, const entt::entity rhs) {

+ 16 - 0
src/entt/entity/group.hpp

@@ -680,6 +680,22 @@ public:
         return !*this || !*length;
         return !*this || !*length;
     }
     }
 
 
+    /**
+     * @brief Direct access to the raw representation offered by the storage.
+     *
+     * @warning
+     * This function is only available for owned types.
+     *
+     * @tparam Component Type of component in which one is interested.
+     * @return A pointer to the array of components.
+     */
+    template<typename Component>
+    [[nodiscard]] auto raw() const ENTT_NOEXCEPT {
+        static_assert((std::is_same_v<Component, Owned> || ...), "Non-owned type");
+        auto *cpool = std::get<storage_type<Component> *>(pools);
+        return cpool ? cpool->raw() : decltype(cpool->raw()){};
+    }
+
     /**
     /**
      * @brief Direct access to the list of entities.
      * @brief Direct access to the list of entities.
      *
      *

+ 21 - 3
src/entt/entity/storage.hpp

@@ -59,11 +59,12 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
     using alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Type>;
     using alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Type>;
     using alloc_traits = std::allocator_traits<alloc_type>;
     using alloc_traits = std::allocator_traits<alloc_type>;
     using alloc_pointer = typename alloc_traits::pointer;
     using alloc_pointer = typename alloc_traits::pointer;
+    using alloc_const_pointer = typename alloc_traits::const_pointer;
 
 
     using bucket_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_pointer>;
     using bucket_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_pointer>;
     using bucket_alloc_traits = std::allocator_traits<bucket_alloc_type>;
     using bucket_alloc_traits = std::allocator_traits<bucket_alloc_type>;
     using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
     using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
-    using bucket_alloc_const_pointer = typename bucket_alloc_traits::const_pointer;
+    using bucket_alloc_const_pointer = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_const_pointer>::const_pointer;
 
 
     using entity_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>;
     using entity_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>;
 
 
@@ -74,7 +75,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
     class storage_iterator final {
     class storage_iterator final {
         friend class basic_storage<Entity, Type>;
         friend class basic_storage<Entity, Type>;
 
 
-        storage_iterator(const bucket_alloc_const_pointer *ref, const typename traits_type::difference_type idx) ENTT_NOEXCEPT
+        storage_iterator(bucket_alloc_pointer const *ref, const typename traits_type::difference_type idx) ENTT_NOEXCEPT
             : packed{ref}, index{idx}
             : packed{ref}, index{idx}
         {}
         {}
 
 
@@ -166,7 +167,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
         }
         }
 
 
     private:
     private:
-        const bucket_alloc_const_pointer *packed;
+        bucket_alloc_pointer const *packed;
         difference_type index;
         difference_type index;
     };
     };
 
 
@@ -270,6 +271,10 @@ public:
     using entity_type = Entity;
     using entity_type = Entity;
     /*! @brief Unsigned integer type. */
     /*! @brief Unsigned integer type. */
     using size_type = std::size_t;
     using size_type = std::size_t;
+    /*! @brief Pointer type to contained elements. */
+    using pointer = bucket_alloc_pointer;
+    /*! @brief Constant pointer type to contained elements. */
+    using const_pointer = bucket_alloc_const_pointer;
     /*! @brief Random access iterator type. */
     /*! @brief Random access iterator type. */
     using iterator = storage_iterator<Type>;
     using iterator = storage_iterator<Type>;
     /*! @brief Constant random access iterator type. */
     /*! @brief Constant random access iterator type. */
@@ -354,6 +359,19 @@ public:
         count ? maybe_resize_packed(count) : reset_to_empty();
         count ? maybe_resize_packed(count) : reset_to_empty();
     }
     }
 
 
+    /**
+     * @brief Direct access to the array of objects.
+     * @return A pointer to the array of objects.
+     */
+    [[nodiscard]] const_pointer raw() const ENTT_NOEXCEPT {
+        return packed;
+    }
+
+    /*! @copydoc raw */
+    [[nodiscard]] pointer raw() ENTT_NOEXCEPT {
+        return packed;
+    }
+
     /**
     /**
      * @brief Returns an iterator to the beginning.
      * @brief Returns an iterator to the beginning.
      *
      *

+ 8 - 0
src/entt/entity/view.hpp

@@ -708,6 +708,14 @@ public:
         return std::get<0>(pools)->empty();
         return std::get<0>(pools)->empty();
     }
     }
 
 
+    /**
+     * @brief Direct access to the raw representation offered by the storage.
+     * @return A pointer to the array of components.
+     */
+    [[nodiscard]] auto raw() const ENTT_NOEXCEPT {
+        return std::get<0>(pools)->raw();
+    }
+
     /**
     /**
      * @brief Direct access to the list of entities.
      * @brief Direct access to the list of entities.
      *
      *

+ 80 - 65
test/entt/entity/group.cpp

@@ -53,7 +53,7 @@ TEST(NonOwningGroup, Functionalities) {
         ASSERT_EQ(cgroup.get<const char>(entity), '2');
         ASSERT_EQ(cgroup.get<const char>(entity), '2');
     }
     }
 
 
-    ASSERT_EQ(*(group.data() + 0), e1);
+    ASSERT_EQ(group.data()[0u], e1);
 
 
     registry.erase<char>(e0);
     registry.erase<char>(e0);
     registry.erase<char>(e1);
     registry.erase<char>(e1);
@@ -221,17 +221,17 @@ TEST(NonOwningGroup, Sort) {
     registry.emplace<int>(e1, 1);
     registry.emplace<int>(e1, 1);
     registry.emplace<int>(e2, 2);
     registry.emplace<int>(e2, 2);
 
 
-    ASSERT_EQ(*(group.data() + 0u), e0);
-    ASSERT_EQ(*(group.data() + 1u), e1);
-    ASSERT_EQ(*(group.data() + 2u), e2);
+    ASSERT_EQ(group.data()[0u], e0);
+    ASSERT_EQ(group.data()[1u], e1);
+    ASSERT_EQ(group.data()[2u], e2);
 
 
     group.sort([](const entt::entity lhs, const entt::entity rhs) {
     group.sort([](const entt::entity lhs, const entt::entity rhs) {
         return entt::to_integral(lhs) < entt::to_integral(rhs);
         return entt::to_integral(lhs) < entt::to_integral(rhs);
     });
     });
 
 
-    ASSERT_EQ(*(group.data() + 0u), e2);
-    ASSERT_EQ(*(group.data() + 1u), e1);
-    ASSERT_EQ(*(group.data() + 2u), e0);
+    ASSERT_EQ(group.data()[0u], e2);
+    ASSERT_EQ(group.data()[1u], e1);
+    ASSERT_EQ(group.data()[2u], e0);
 
 
     ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
     ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
     ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
     ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
@@ -243,9 +243,9 @@ TEST(NonOwningGroup, Sort) {
         return lhs > rhs;
         return lhs > rhs;
     });
     });
 
 
-    ASSERT_EQ(*(group.data() + 0u), e0);
-    ASSERT_EQ(*(group.data() + 1u), e1);
-    ASSERT_EQ(*(group.data() + 2u), e2);
+    ASSERT_EQ(group.data()[0u], e0);
+    ASSERT_EQ(group.data()[1u], e1);
+    ASSERT_EQ(group.data()[2u], e2);
 
 
     ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
     ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
     ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
     ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
@@ -259,9 +259,9 @@ TEST(NonOwningGroup, Sort) {
         return std::get<0>(lhs) < std::get<0>(rhs);
         return std::get<0>(lhs) < std::get<0>(rhs);
     });
     });
 
 
-    ASSERT_EQ(*(group.data() + 0u), e2);
-    ASSERT_EQ(*(group.data() + 1u), e1);
-    ASSERT_EQ(*(group.data() + 2u), e0);
+    ASSERT_EQ(group.data()[0u], e2);
+    ASSERT_EQ(group.data()[1u], e1);
+    ASSERT_EQ(group.data()[2u], e0);
 
 
     ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
     ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
     ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
     ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
@@ -665,8 +665,8 @@ TEST(OwningGroup, Functionalities) {
 
 
     ASSERT_EQ(group.size(), 1u);
     ASSERT_EQ(group.size(), 1u);
 
 
-    ASSERT_EQ(*(cgroup.rbegin() + 0), e1);
-    ASSERT_EQ(*(group.rbegin() + 0), e1);
+    ASSERT_EQ(cgroup.raw<const int>()[0u][0u], 42);
+    ASSERT_EQ(group.raw<int>()[0u][0u], 42);
 
 
     for(auto entity: group) {
     for(auto entity: group) {
         ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
         ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
@@ -674,7 +674,8 @@ TEST(OwningGroup, Functionalities) {
         ASSERT_EQ(cgroup.get<const char>(entity), '2');
         ASSERT_EQ(cgroup.get<const char>(entity), '2');
     }
     }
 
 
-    ASSERT_EQ(*(group.data() + 0), e1);
+    ASSERT_EQ(group.data()[0u], e1);
+    ASSERT_EQ(group.raw<int>()[0u][0u], 42);
 
 
     registry.erase<char>(e0);
     registry.erase<char>(e0);
     registry.erase<char>(e1);
     registry.erase<char>(e1);
@@ -705,6 +706,7 @@ TEST(OwningGroup, Invalid) {
     ASSERT_TRUE(group.empty());
     ASSERT_TRUE(group.empty());
     ASSERT_EQ(group.size(), 0u);
     ASSERT_EQ(group.size(), 0u);
 
 
+    ASSERT_EQ(group.raw<const int>(), nullptr);
     ASSERT_EQ(group.data(), nullptr);
     ASSERT_EQ(group.data(), nullptr);
 
 
     ASSERT_EQ(group.begin(), group.end());
     ASSERT_EQ(group.begin(), group.end());
@@ -839,17 +841,21 @@ TEST(OwningGroup, SortOrdered) {
         return group.get<boxed_int>(lhs).value < group.get<boxed_int>(rhs).value;
         return group.get<boxed_int>(lhs).value < group.get<boxed_int>(rhs).value;
     });
     });
 
 
-    ASSERT_EQ(*(group.data() + 0u), entities[0]);
-    ASSERT_EQ(*(group.data() + 1u), entities[1]);
-    ASSERT_EQ(*(group.data() + 2u), entities[2]);
+    ASSERT_EQ(group.data()[0u], entities[0]);
+    ASSERT_EQ(group.data()[1u], entities[1]);
+    ASSERT_EQ(group.data()[2u], entities[2]);
+    ASSERT_EQ(group.data()[3u], entities[3]);
+    ASSERT_EQ(group.data()[4u], entities[4]);
 
 
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 12);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 9);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 6);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 12);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 9);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 6);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 1);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][4u].value, 2);
 
 
-    ASSERT_EQ(group.get<char>(*(group.data() + 0u)), 'a');
-    ASSERT_EQ(group.get<char>(*(group.data() + 1u)), 'b');
-    ASSERT_EQ(group.get<char>(*(group.data() + 2u)), 'c');
+    ASSERT_EQ(group.raw<char>()[0u][0u], 'a');
+    ASSERT_EQ(group.raw<char>()[0u][1u], 'b');
+    ASSERT_EQ(group.raw<char>()[0u][2u], 'c');
 
 
     ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
@@ -882,17 +888,21 @@ TEST(OwningGroup, SortReverse) {
         return lhs.value < rhs.value;
         return lhs.value < rhs.value;
     });
     });
 
 
-    ASSERT_EQ(*(group.data() + 0u), entities[2]);
-    ASSERT_EQ(*(group.data() + 1u), entities[1]);
-    ASSERT_EQ(*(group.data() + 2u), entities[0]);
+    ASSERT_EQ(group.data()[0u], entities[2]);
+    ASSERT_EQ(group.data()[1u], entities[1]);
+    ASSERT_EQ(group.data()[2u], entities[0]);
+    ASSERT_EQ(group.data()[3u], entities[3]);
+    ASSERT_EQ(group.data()[4u], entities[4]);
 
 
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 12);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 9);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 6);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 12);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 9);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 6);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 1);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][4u].value, 2);
 
 
-    ASSERT_EQ(group.get<char>(*(group.data() + 0u)), 'c');
-    ASSERT_EQ(group.get<char>(*(group.data() + 1u)), 'b');
-    ASSERT_EQ(group.get<char>(*(group.data() + 2u)), 'a');
+    ASSERT_EQ(group.raw<char>()[0u][0u], 'c');
+    ASSERT_EQ(group.raw<char>()[0u][1u], 'b');
+    ASSERT_EQ(group.raw<char>()[0u][2u], 'a');
 
 
     ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
     ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
@@ -933,29 +943,27 @@ TEST(OwningGroup, SortUnordered) {
         return std::get<1>(lhs) < std::get<1>(rhs);
         return std::get<1>(lhs) < std::get<1>(rhs);
     });
     });
 
 
-    ASSERT_EQ(*(group.data() + 0u), entities[4]);
-    ASSERT_EQ(*(group.data() + 1u), entities[3]);
-    ASSERT_EQ(*(group.data() + 2u), entities[0]);
-    ASSERT_EQ(*(group.data() + 3u), entities[1]);
-    ASSERT_EQ(*(group.data() + 4u), entities[2]);
-
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 12);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 9);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 6);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 3u)).value, 3);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 4u)).value, 1);
-
-    ASSERT_EQ(group.get<char>(*(group.data() + 0u)), 'e');
-    ASSERT_EQ(group.get<char>(*(group.data() + 1u)), 'd');
-    ASSERT_EQ(group.get<char>(*(group.data() + 2u)), 'c');
-    ASSERT_EQ(group.get<char>(*(group.data() + 3u)), 'b');
-    ASSERT_EQ(group.get<char>(*(group.data() + 4u)), 'a');
-
-    ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'c')));
-    ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{3}, 'b')));
-    ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{1}, 'a')));
-    ASSERT_EQ((group.get<boxed_int, char>(entities[3])), (std::make_tuple(boxed_int{9}, 'd')));
-    ASSERT_EQ((group.get<boxed_int, char>(entities[4])), (std::make_tuple(boxed_int{12}, 'e')));
+    ASSERT_EQ(group.data()[0u], entities[4]);
+    ASSERT_EQ(group.data()[1u], entities[3]);
+    ASSERT_EQ(group.data()[2u], entities[0]);
+    ASSERT_EQ(group.data()[3u], entities[1]);
+    ASSERT_EQ(group.data()[4u], entities[2]);
+    ASSERT_EQ(group.data()[5u], entities[5]);
+    ASSERT_EQ(group.data()[6u], entities[6]);
+
+    ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 12);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 9);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 6);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 3);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][4u].value, 1);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][5u].value, 4);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][6u].value, 5);
+
+    ASSERT_EQ(group.get<char>(group.data()[0u]), 'e');
+    ASSERT_EQ(group.get<char>(group.data()[1u]), 'd');
+    ASSERT_EQ(group.get<char>(group.data()[2u]), 'c');
+    ASSERT_EQ(group.get<char>(group.data()[3u]), 'b');
+    ASSERT_EQ(group.get<char>(group.data()[4u]), 'a');
 
 
     ASSERT_FALSE(group.contains(entities[5]));
     ASSERT_FALSE(group.contains(entities[5]));
     ASSERT_FALSE(group.contains(entities[6]));
     ASSERT_FALSE(group.contains(entities[6]));
@@ -980,15 +988,20 @@ TEST(OwningGroup, SortWithExclusionList) {
         return lhs < rhs;
         return lhs < rhs;
     });
     });
 
 
-    ASSERT_EQ(*(group.data() + 0u), entities[4]);
-    ASSERT_EQ(*(group.data() + 1u), entities[3]);
-    ASSERT_EQ(*(group.data() + 2u), entities[1]);
-    ASSERT_EQ(*(group.data() + 3u), entities[0]);
+    ASSERT_EQ(group.data()[0u], entities[4]);
+    ASSERT_EQ(group.data()[1u], entities[3]);
+    ASSERT_EQ(group.data()[2u], entities[1]);
+    ASSERT_EQ(group.data()[3u], entities[0]);
 
 
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 4);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 3);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 1);
-    ASSERT_EQ(group.get<boxed_int>(*(group.data() + 3u)).value, 0);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 4);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 3);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 1);
+    ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 0);
+
+    ASSERT_EQ(group.get<boxed_int>(entities[0]).value, 0);
+    ASSERT_EQ(group.get<boxed_int>(entities[1]).value, 1);
+    ASSERT_EQ(group.get<boxed_int>(entities[3]).value, 3);
+    ASSERT_EQ(group.get<boxed_int>(entities[4]).value, 4);
 
 
     ASSERT_FALSE(group.contains(entities[2]));
     ASSERT_FALSE(group.contains(entities[2]));
 }
 }
@@ -1049,6 +1062,8 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
     static_assert(std::is_same_v<decltype(group.get<int, const char, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
     static_assert(std::is_same_v<decltype(group.get<int, const char, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
     static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>);
     static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>);
     static_assert(std::is_same_v<decltype(group.data()), const entt::entity *>);
     static_assert(std::is_same_v<decltype(group.data()), const entt::entity *>);
+    static_assert(std::is_same_v<decltype(group.raw<const char>()), const char * const *>);
+    static_assert(std::is_same_v<decltype(group.raw<int>()), int **>);
 
 
     group.each([](auto &&i, auto &&c, auto &&d, auto &&f) {
     group.each([](auto &&i, auto &&c, auto &&d, auto &&f) {
         static_assert(std::is_same_v<decltype(i), int &>);
         static_assert(std::is_same_v<decltype(i), int &>);

+ 1 - 0
test/entt/entity/registry_no_eto.cpp

@@ -14,6 +14,7 @@ TEST(Registry, NoEto) {
     registry.emplace<empty_type>(entity);
     registry.emplace<empty_type>(entity);
     registry.emplace<int>(entity, 42);
     registry.emplace<int>(entity, 42);
 
 
+    ASSERT_NE(registry.view<empty_type>().raw(), nullptr);
     ASSERT_NE(registry.try_get<empty_type>(entity), nullptr);
     ASSERT_NE(registry.try_get<empty_type>(entity), nullptr);
     ASSERT_EQ(registry.view<empty_type>().get(entity), std::as_const(registry).view<const empty_type>().get(entity));
     ASSERT_EQ(registry.view<empty_type>().get(entity), std::as_const(registry).view<const empty_type>().get(entity));
 
 

+ 19 - 3
test/entt/entity/storage.cpp

@@ -418,6 +418,22 @@ TEST(Storage, ConstReverseIterator) {
     ASSERT_GE(cend, pool.crend());
     ASSERT_GE(cend, pool.crend());
 }
 }
 
 
+TEST(Storage, Raw) {
+    entt::storage<int> pool;
+
+    pool.emplace(entt::entity{3}, 3);
+    pool.emplace(entt::entity{12}, 6);
+    pool.emplace(entt::entity{42}, 9);
+
+    ASSERT_EQ(pool.get(entt::entity{3}), 3);
+    ASSERT_EQ(std::as_const(pool).get(entt::entity{12}), 6);
+    ASSERT_EQ(pool.get(entt::entity{42}), 9);
+
+    ASSERT_EQ(pool.raw()[0u][0u], 3);
+    ASSERT_EQ(std::as_const(pool).raw()[0u][1u], 6);
+    ASSERT_EQ(pool.raw()[0u][2u], 9);
+}
+
 TEST(Storage, SortOrdered) {
 TEST(Storage, SortOrdered) {
     entt::storage<boxed_int> pool;
     entt::storage<boxed_int> pool;
     entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
     entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
@@ -480,9 +496,9 @@ TEST(Storage, SortRange) {
 
 
     pool.sort_n(2u, [](auto lhs, auto rhs) { return lhs.value < rhs.value; });
     pool.sort_n(2u, [](auto lhs, auto rhs) { return lhs.value < rhs.value; });
 
 
-    ASSERT_EQ(pool.rbegin()[0u], boxed_int{6});
-    ASSERT_EQ(pool.rbegin()[1u], boxed_int{3});
-    ASSERT_EQ(pool.rbegin()[2u], boxed_int{1});
+    ASSERT_EQ(pool.raw()[0u][0u], boxed_int{6});
+    ASSERT_EQ(pool.raw()[0u][1u], boxed_int{3});
+    ASSERT_EQ(pool.raw()[0u][2u], boxed_int{1});
 
 
     ASSERT_EQ(pool.data()[0u], entt::entity{42});
     ASSERT_EQ(pool.data()[0u], entt::entity{42});
     ASSERT_EQ(pool.data()[1u], entt::entity{12});
     ASSERT_EQ(pool.data()[1u], entt::entity{12});

+ 18 - 5
test/entt/entity/view.cpp

@@ -44,8 +44,11 @@ TEST(SingleComponentView, Functionalities) {
         ASSERT_TRUE(cview.get<const char>(entity) == '1' || std::get<const char &>(cview.get(entity)) == '2');
         ASSERT_TRUE(cview.get<const char>(entity) == '1' || std::get<const char &>(cview.get(entity)) == '2');
     }
     }
 
 
-    ASSERT_EQ(*(view.data() + 0), e1);
-    ASSERT_EQ(*(view.data() + 1), e0);
+    ASSERT_EQ(view.data()[0u], e1);
+    ASSERT_EQ(view.data()[1u], e0);
+
+    ASSERT_EQ(view.raw()[0u][0u], '2');
+    ASSERT_EQ(cview.raw()[0u][1u], '1');
 
 
     registry.erase<char>(e0);
     registry.erase<char>(e0);
     registry.erase<char>(e1);
     registry.erase<char>(e1);
@@ -61,7 +64,7 @@ TEST(SingleComponentView, Functionalities) {
     ASSERT_FALSE(invalid);
     ASSERT_FALSE(invalid);
 }
 }
 
 
-TEST(SingleComponentView, Data) {
+TEST(SingleComponentView, RawData) {
     entt::registry registry;
     entt::registry registry;
     auto view = registry.view<int>();
     auto view = registry.view<int>();
     auto cview = std::as_const(registry).view<const int>();
     auto cview = std::as_const(registry).view<const int>();
@@ -70,6 +73,8 @@ TEST(SingleComponentView, Data) {
 
 
     ASSERT_EQ(view.size(), 0u);
     ASSERT_EQ(view.size(), 0u);
     ASSERT_EQ(cview.size(), 0u);
     ASSERT_EQ(cview.size(), 0u);
+    ASSERT_EQ(view.raw(), nullptr);
+    ASSERT_EQ(cview.raw(), nullptr);
     ASSERT_EQ(view.data(), nullptr);
     ASSERT_EQ(view.data(), nullptr);
     ASSERT_EQ(cview.data(), nullptr);
     ASSERT_EQ(cview.data(), nullptr);
 
 
@@ -77,8 +82,10 @@ TEST(SingleComponentView, Data) {
 
 
     ASSERT_NE(view.size(), 0u);
     ASSERT_NE(view.size(), 0u);
     ASSERT_NE(cview.size(), 0u);
     ASSERT_NE(cview.size(), 0u);
-    ASSERT_EQ(*view.data(), entity);
-    ASSERT_EQ(*cview.data(), entity);
+    ASSERT_EQ(view.raw()[0u][0u], 42);
+    ASSERT_EQ(cview.raw()[0u][0u], 42);
+    ASSERT_EQ(view.data()[0u], entity);
+    ASSERT_EQ(cview.data()[0u], entity);
 
 
     registry.destroy(entity);
     registry.destroy(entity);
 
 
@@ -98,6 +105,7 @@ TEST(SingleComponentView, LazyTypeFromConstRegistry) {
     ASSERT_TRUE(cview);
     ASSERT_TRUE(cview);
     ASSERT_TRUE(eview);
     ASSERT_TRUE(eview);
 
 
+    ASSERT_NE(cview.raw(), nullptr);
     ASSERT_NE(eview.data(), nullptr);
     ASSERT_NE(eview.data(), nullptr);
 
 
     ASSERT_FALSE(cview.empty());
     ASSERT_FALSE(cview.empty());
@@ -218,10 +226,15 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
     ASSERT_EQ(view.size(), 1u);
     ASSERT_EQ(view.size(), 1u);
     ASSERT_EQ(cview.size(), 1u);
     ASSERT_EQ(cview.size(), 1u);
 
 
+    static_assert(std::is_same_v<decltype(view.raw()), int **>);
+    static_assert(std::is_same_v<decltype(cview.raw()), const int * const *>);
+
     static_assert(std::is_same_v<decltype(view.get<int>({})), int &>);
     static_assert(std::is_same_v<decltype(view.get<int>({})), int &>);
     static_assert(std::is_same_v<decltype(view.get({})), std::tuple<int &>>);
     static_assert(std::is_same_v<decltype(view.get({})), std::tuple<int &>>);
+    static_assert(std::is_same_v<decltype(view.raw()), int **>);
     static_assert(std::is_same_v<decltype(cview.get<const int>({})), const int &>);
     static_assert(std::is_same_v<decltype(cview.get<const int>({})), const int &>);
     static_assert(std::is_same_v<decltype(cview.get({})), std::tuple<const int &>>);
     static_assert(std::is_same_v<decltype(cview.get({})), std::tuple<const int &>>);
+    static_assert(std::is_same_v<decltype(cview.raw()), const int * const *>);
 
 
     view.each([](auto &&i) {
     view.each([](auto &&i) {
         static_assert(std::is_same_v<decltype(i), int &>);
         static_assert(std::is_same_v<decltype(i), int &>);