diff --git a/docs/md/entity.md b/docs/md/entity.md index 1aae64f94..7d26a66e6 100644 --- a/docs/md/entity.md +++ b/docs/md/entity.md @@ -10,6 +10,7 @@ * [Type-less and bitset-free](#type-less-and-bitset-free) * [Build your own](#build-your-own) * [Pay per use](#pay-per-use) + * [All or nothing](#all-or-nothing) * [Vademecum](#vademecum) * [Pools](#pools) * [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 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`.
+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.
+The rest is experimentation and the desire to invent something new, hoping to +have succeeded. + # Vademecum 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 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 -as a single component view. In fact, they walk through a packed array of -components and return them one at a time.
-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.
+as a single component view. In fact, they walk through a packed (actually paged) +array of components and return them one at a time.
+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.
Refer to the inline documentation for all the details. 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 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.
+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.
Refer to the inline documentation for all the details. 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.
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 -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 registry.sort([](const entt::entity lhs, const entt::entity rhs) { diff --git a/src/entt/entity/group.hpp b/src/entt/entity/group.hpp index dc143a745..f0ace4448 100644 --- a/src/entt/entity/group.hpp +++ b/src/entt/entity/group.hpp @@ -680,6 +680,22 @@ public: 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 + [[nodiscard]] auto raw() const ENTT_NOEXCEPT { + static_assert((std::is_same_v || ...), "Non-owned type"); + auto *cpool = std::get *>(pools); + return cpool ? cpool->raw() : decltype(cpool->raw()){}; + } + /** * @brief Direct access to the list of entities. * diff --git a/src/entt/entity/storage.hpp b/src/entt/entity/storage.hpp index 037aac224..9d5be1230 100644 --- a/src/entt/entity/storage.hpp +++ b/src/entt/entity/storage.hpp @@ -59,11 +59,12 @@ class basic_storage: public basic_sparse_set::template rebind_alloc; using alloc_traits = std::allocator_traits; using alloc_pointer = typename alloc_traits::pointer; + using alloc_const_pointer = typename alloc_traits::const_pointer; using bucket_alloc_type = typename std::allocator_traits::template rebind_alloc; using bucket_alloc_traits = std::allocator_traits; 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::template rebind_alloc::const_pointer; using entity_alloc_type = typename std::allocator_traits::template rebind_alloc; @@ -74,7 +75,7 @@ class basic_storage: public basic_sparse_set; - 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} {} @@ -166,7 +167,7 @@ class basic_storage: public basic_sparse_set; /*! @brief Constant random access iterator type. */ @@ -354,6 +359,19 @@ public: 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. * diff --git a/src/entt/entity/view.hpp b/src/entt/entity/view.hpp index 6b44ba78e..bd8de4cf8 100644 --- a/src/entt/entity/view.hpp +++ b/src/entt/entity/view.hpp @@ -708,6 +708,14 @@ public: 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. * diff --git a/test/entt/entity/group.cpp b/test/entt/entity/group.cpp index 97991ab9a..a24a600e0 100644 --- a/test/entt/entity/group.cpp +++ b/test/entt/entity/group.cpp @@ -53,7 +53,7 @@ TEST(NonOwningGroup, Functionalities) { ASSERT_EQ(cgroup.get(entity), '2'); } - ASSERT_EQ(*(group.data() + 0), e1); + ASSERT_EQ(group.data()[0u], e1); registry.erase(e0); registry.erase(e1); @@ -221,17 +221,17 @@ TEST(NonOwningGroup, Sort) { registry.emplace(e1, 1); registry.emplace(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) { 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(e0)), (std::make_tuple(0, 0u))); ASSERT_EQ((group.get(e1)), (std::make_tuple(1, 1u))); @@ -243,9 +243,9 @@ TEST(NonOwningGroup, Sort) { 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(e0)), (std::make_tuple(0, 0u))); ASSERT_EQ((group.get(e1)), (std::make_tuple(1, 1u))); @@ -259,9 +259,9 @@ TEST(NonOwningGroup, Sort) { 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(e0)), (std::make_tuple(0, 0u))); ASSERT_EQ((group.get(e1)), (std::make_tuple(1, 1u))); @@ -665,8 +665,8 @@ TEST(OwningGroup, Functionalities) { ASSERT_EQ(group.size(), 1u); - ASSERT_EQ(*(cgroup.rbegin() + 0), e1); - ASSERT_EQ(*(group.rbegin() + 0), e1); + ASSERT_EQ(cgroup.raw()[0u][0u], 42); + ASSERT_EQ(group.raw()[0u][0u], 42); for(auto entity: group) { ASSERT_EQ(std::get<0>(cgroup.get(entity)), 42); @@ -674,7 +674,8 @@ TEST(OwningGroup, Functionalities) { ASSERT_EQ(cgroup.get(entity), '2'); } - ASSERT_EQ(*(group.data() + 0), e1); + ASSERT_EQ(group.data()[0u], e1); + ASSERT_EQ(group.raw()[0u][0u], 42); registry.erase(e0); registry.erase(e1); @@ -705,6 +706,7 @@ TEST(OwningGroup, Invalid) { ASSERT_TRUE(group.empty()); ASSERT_EQ(group.size(), 0u); + ASSERT_EQ(group.raw(), nullptr); ASSERT_EQ(group.data(), nullptr); ASSERT_EQ(group.begin(), group.end()); @@ -839,17 +841,21 @@ TEST(OwningGroup, SortOrdered) { return group.get(lhs).value < group.get(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(*(group.data() + 0u)).value, 12); - ASSERT_EQ(group.get(*(group.data() + 1u)).value, 9); - ASSERT_EQ(group.get(*(group.data() + 2u)).value, 6); + ASSERT_EQ(group.raw()[0u][0u].value, 12); + ASSERT_EQ(group.raw()[0u][1u].value, 9); + ASSERT_EQ(group.raw()[0u][2u].value, 6); + ASSERT_EQ(group.raw()[0u][3u].value, 1); + ASSERT_EQ(group.raw()[0u][4u].value, 2); - ASSERT_EQ(group.get(*(group.data() + 0u)), 'a'); - ASSERT_EQ(group.get(*(group.data() + 1u)), 'b'); - ASSERT_EQ(group.get(*(group.data() + 2u)), 'c'); + ASSERT_EQ(group.raw()[0u][0u], 'a'); + ASSERT_EQ(group.raw()[0u][1u], 'b'); + ASSERT_EQ(group.raw()[0u][2u], 'c'); ASSERT_EQ((group.get(entities[0])), (std::make_tuple(boxed_int{12}, 'a'))); ASSERT_EQ((group.get(entities[1])), (std::make_tuple(boxed_int{9}, 'b'))); @@ -882,17 +888,21 @@ TEST(OwningGroup, SortReverse) { 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(*(group.data() + 0u)).value, 12); - ASSERT_EQ(group.get(*(group.data() + 1u)).value, 9); - ASSERT_EQ(group.get(*(group.data() + 2u)).value, 6); + ASSERT_EQ(group.raw()[0u][0u].value, 12); + ASSERT_EQ(group.raw()[0u][1u].value, 9); + ASSERT_EQ(group.raw()[0u][2u].value, 6); + ASSERT_EQ(group.raw()[0u][3u].value, 1); + ASSERT_EQ(group.raw()[0u][4u].value, 2); - ASSERT_EQ(group.get(*(group.data() + 0u)), 'c'); - ASSERT_EQ(group.get(*(group.data() + 1u)), 'b'); - ASSERT_EQ(group.get(*(group.data() + 2u)), 'a'); + ASSERT_EQ(group.raw()[0u][0u], 'c'); + ASSERT_EQ(group.raw()[0u][1u], 'b'); + ASSERT_EQ(group.raw()[0u][2u], 'a'); ASSERT_EQ((group.get(entities[0])), (std::make_tuple(boxed_int{6}, 'a'))); ASSERT_EQ((group.get(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); }); - 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()[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.get(*(group.data() + 0u)).value, 12); - ASSERT_EQ(group.get(*(group.data() + 1u)).value, 9); - ASSERT_EQ(group.get(*(group.data() + 2u)).value, 6); - ASSERT_EQ(group.get(*(group.data() + 3u)).value, 3); - ASSERT_EQ(group.get(*(group.data() + 4u)).value, 1); + ASSERT_EQ(group.raw()[0u][0u].value, 12); + ASSERT_EQ(group.raw()[0u][1u].value, 9); + ASSERT_EQ(group.raw()[0u][2u].value, 6); + ASSERT_EQ(group.raw()[0u][3u].value, 3); + ASSERT_EQ(group.raw()[0u][4u].value, 1); + ASSERT_EQ(group.raw()[0u][5u].value, 4); + ASSERT_EQ(group.raw()[0u][6u].value, 5); - ASSERT_EQ(group.get(*(group.data() + 0u)), 'e'); - ASSERT_EQ(group.get(*(group.data() + 1u)), 'd'); - ASSERT_EQ(group.get(*(group.data() + 2u)), 'c'); - ASSERT_EQ(group.get(*(group.data() + 3u)), 'b'); - ASSERT_EQ(group.get(*(group.data() + 4u)), 'a'); - - ASSERT_EQ((group.get(entities[0])), (std::make_tuple(boxed_int{6}, 'c'))); - ASSERT_EQ((group.get(entities[1])), (std::make_tuple(boxed_int{3}, 'b'))); - ASSERT_EQ((group.get(entities[2])), (std::make_tuple(boxed_int{1}, 'a'))); - ASSERT_EQ((group.get(entities[3])), (std::make_tuple(boxed_int{9}, 'd'))); - ASSERT_EQ((group.get(entities[4])), (std::make_tuple(boxed_int{12}, 'e'))); + ASSERT_EQ(group.get(group.data()[0u]), 'e'); + ASSERT_EQ(group.get(group.data()[1u]), 'd'); + ASSERT_EQ(group.get(group.data()[2u]), 'c'); + ASSERT_EQ(group.get(group.data()[3u]), 'b'); + ASSERT_EQ(group.get(group.data()[4u]), 'a'); ASSERT_FALSE(group.contains(entities[5])); ASSERT_FALSE(group.contains(entities[6])); @@ -980,15 +988,20 @@ TEST(OwningGroup, SortWithExclusionList) { 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(*(group.data() + 0u)).value, 4); - ASSERT_EQ(group.get(*(group.data() + 1u)).value, 3); - ASSERT_EQ(group.get(*(group.data() + 2u)).value, 1); - ASSERT_EQ(group.get(*(group.data() + 3u)).value, 0); + ASSERT_EQ(group.raw()[0u][0u].value, 4); + ASSERT_EQ(group.raw()[0u][1u].value, 3); + ASSERT_EQ(group.raw()[0u][2u].value, 1); + ASSERT_EQ(group.raw()[0u][3u].value, 0); + + ASSERT_EQ(group.get(entities[0]).value, 0); + ASSERT_EQ(group.get(entities[1]).value, 1); + ASSERT_EQ(group.get(entities[3]).value, 3); + ASSERT_EQ(group.get(entities[4]).value, 4); ASSERT_FALSE(group.contains(entities[2])); } @@ -1049,6 +1062,8 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) { static_assert(std::is_same_v({})), std::tuple>); static_assert(std::is_same_v>); static_assert(std::is_same_v); + static_assert(std::is_same_v()), const char * const *>); + static_assert(std::is_same_v()), int **>); group.each([](auto &&i, auto &&c, auto &&d, auto &&f) { static_assert(std::is_same_v); diff --git a/test/entt/entity/registry_no_eto.cpp b/test/entt/entity/registry_no_eto.cpp index 63224b9ec..a6a917a49 100644 --- a/test/entt/entity/registry_no_eto.cpp +++ b/test/entt/entity/registry_no_eto.cpp @@ -14,6 +14,7 @@ TEST(Registry, NoEto) { registry.emplace(entity); registry.emplace(entity, 42); + ASSERT_NE(registry.view().raw(), nullptr); ASSERT_NE(registry.try_get(entity), nullptr); ASSERT_EQ(registry.view().get(entity), std::as_const(registry).view().get(entity)); diff --git a/test/entt/entity/storage.cpp b/test/entt/entity/storage.cpp index b2e75170d..33344791f 100644 --- a/test/entt/entity/storage.cpp +++ b/test/entt/entity/storage.cpp @@ -418,6 +418,22 @@ TEST(Storage, ConstReverseIterator) { ASSERT_GE(cend, pool.crend()); } +TEST(Storage, Raw) { + entt::storage 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) { entt::storage pool; 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; }); - 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()[1u], entt::entity{12}); diff --git a/test/entt/entity/view.cpp b/test/entt/entity/view.cpp index 967140dd9..24914ba67 100644 --- a/test/entt/entity/view.cpp +++ b/test/entt/entity/view.cpp @@ -44,8 +44,11 @@ TEST(SingleComponentView, Functionalities) { ASSERT_TRUE(cview.get(entity) == '1' || std::get(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(e0); registry.erase(e1); @@ -61,7 +64,7 @@ TEST(SingleComponentView, Functionalities) { ASSERT_FALSE(invalid); } -TEST(SingleComponentView, Data) { +TEST(SingleComponentView, RawData) { entt::registry registry; auto view = registry.view(); auto cview = std::as_const(registry).view(); @@ -70,6 +73,8 @@ TEST(SingleComponentView, Data) { ASSERT_EQ(view.size(), 0u); ASSERT_EQ(cview.size(), 0u); + ASSERT_EQ(view.raw(), nullptr); + ASSERT_EQ(cview.raw(), nullptr); ASSERT_EQ(view.data(), nullptr); ASSERT_EQ(cview.data(), nullptr); @@ -77,8 +82,10 @@ TEST(SingleComponentView, Data) { ASSERT_NE(view.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); @@ -98,6 +105,7 @@ TEST(SingleComponentView, LazyTypeFromConstRegistry) { ASSERT_TRUE(cview); ASSERT_TRUE(eview); + ASSERT_NE(cview.raw(), nullptr); ASSERT_NE(eview.data(), nullptr); ASSERT_FALSE(cview.empty()); @@ -218,10 +226,15 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetween) { ASSERT_EQ(view.size(), 1u); ASSERT_EQ(cview.size(), 1u); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v({})), int &>); static_assert(std::is_same_v>); + static_assert(std::is_same_v); static_assert(std::is_same_v({})), const int &>); static_assert(std::is_same_v>); + static_assert(std::is_same_v); view.each([](auto &&i) { static_assert(std::is_same_v);