diff --git a/TODO b/TODO index 8f45c5408..9af1a6020 100644 --- a/TODO +++ b/TODO @@ -22,4 +22,7 @@ * early out in views using bitmasks with bloom filter like access based on modulus - standard each, use bitmask to speed up the whole thing and avoid accessing the pools to test for the page - iterator based each with a couple of iterators passed from outside (use bitmask + has) +* multi component registry::remove and some others? * reactive systems + +* update doc + inline doc (instance-less pools for empty types) diff --git a/docs/md/entity.md b/docs/md/entity.md index d46036317..85f253ee6 100644 --- a/docs/md/entity.md +++ b/docs/md/entity.md @@ -1460,25 +1460,21 @@ groups or as free types with multi component views and groups in general. An empty type `T` is such that `std::is_empty_v` returns true. They are also the same types for which _empty base optimization_ (EBO) is possibile.
`EnTT` handles these types in a special way, optimizing both in terms of -performance and memory usage. However, this also has drawbacks that are worth +performance and memory usage. However, this also has consequences that are worth mentioning. -When an empty type is detected, a pool is created in such a way that one and -only one instance of the given type is created. All the entities will refer to -it. Since the type is, in fact, empty, this is safe and there is no risk that a -modification of its data members will affect other instances.
-Iterations are faster because only the entities to which the type is assigned -are considered. Similarly, less memory is used, since there exists always only -one instance of the component itself, no matter how many entities it is -assigned. +When an empty type is detected, it's not instantiated in any case. Therefore, +only the entities to which it's assigned are made availble. All the iterators as +well as the `get` member functions of registries, views and groups will return +temporary objects. Similarly, some functions such as `try_get` or the raw access +to the list of components aren't available for this kind of types.
+On the other hand, iterations are faster because only the entities to which the +type is assigned are considered. Moreover, less memory is used, since there +doesn't exist any instance of the component, no matter how many entities it is +assigned to. -The drawback is that the `raw` member function will no longer be able to return -a valid pointer to the list of components in the pool. This is because there is -no list of components at all. Only one instance of the given type exists in this -case. Therefore, `raw` will always return a pointer to that instance.
-Nonetheless, the iterators returned by the `begin` and `end` member functions -are still valid and can be used safely. More in general, all the features -offered by the library aren't affected, but for the `raw` member function. +More in general, none of the features offered by the library is affected, but +for the ones that require to return actual instances. # Multithreading diff --git a/src/entt/entity/actor.hpp b/src/entt/entity/actor.hpp index 7715f80da..6340eb628 100644 --- a/src/entt/entity/actor.hpp +++ b/src/entt/entity/actor.hpp @@ -92,7 +92,7 @@ struct basic_actor { * @return A reference to the newly created component. */ template - Component & assign(Args &&... args) { + decltype(auto) assign(Args &&... args) { return reg->template assign_or_replace(entt, std::forward(args)...); } diff --git a/src/entt/entity/group.hpp b/src/entt/entity/group.hpp index 18aac9d11..e8933b925 100644 --- a/src/entt/entity/group.hpp +++ b/src/entt/entity/group.hpp @@ -164,11 +164,6 @@ public: * 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. */ @@ -296,14 +291,13 @@ public: * @return The components assigned to the entity. */ template - std::conditional_t>, std::tuple> - get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { + decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { ENTT_ASSERT(contains(entt)); if constexpr(sizeof...(Component) == 1) { return (std::get *>(pools)->get(entt), ...); } else { - return std::tuple{get(entt)...}; + return std::tuple(entt))...>{get(entt)...}; } } @@ -322,13 +316,18 @@ public: * void(Get &...); * @endcode * + * @note + * Empty types aren't explicitly instantiated. Therefore, temporary objects + * are returned during iterations. They can be caught only by copy or with + * const references. + * * @tparam Func Type of the function object to invoke. * @param func A valid function object. */ template inline void each(Func func) const { for(const auto entt: *handler) { - if constexpr(std::is_invocable_v...>) { + if constexpr(std::is_invocable_v({}))...>) { func(std::get *>(pools)->get(entt)...); } else { func(entt, std::get *>(pools)->get(entt)...); @@ -422,14 +421,30 @@ class basic_group, Owned...> { using component_iterator_type = decltype(std::declval>().begin()); template - const Component & from_index(const typename sparse_set::size_type index) { + decltype(auto) from_index(const typename sparse_set::size_type index) { if constexpr(std::disjunction_v...>) { - return std::get *>(pools)->raw()[index]; + if constexpr(std::is_empty_v) { + return std::get *>(pools).get(); + } else { + return std::as_const(*std::get *>(pools)).raw()[index]; + } } else { - return std::get *>(pools)->get(data()[index]); + return std::as_const(*std::get *>(pools)).get(data()[index]); } } + template + inline auto swap(int, pool_type *cpool, const std::size_t lhs, const std::size_t rhs) + -> decltype(cpool->raw(), void()) { + std::swap(cpool->raw()[lhs], cpool->raw()[rhs]); + cpool->swap(lhs, rhs); + } + + template + inline void swap(char, pool_type *cpool, const std::size_t lhs, const std::size_t rhs) { + cpool->swap(lhs, rhs); + } + // we could use pool_type *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug) basic_group(const typename basic_registry::size_type *sz, storage> *... owned, storage> *... get) ENTT_NOEXCEPT : length{sz}, @@ -495,11 +510,6 @@ public: * 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. */ @@ -630,14 +640,13 @@ public: * @return The components assigned to the entity. */ template - std::conditional_t>, std::tuple> - get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { + decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { ENTT_ASSERT(contains(entt)); if constexpr(sizeof...(Component) == 1) { return (std::get *>(pools)->get(entt), ...); } else { - return std::tuple{get(entt)...}; + return std::tuple(entt))...>{get(entt)...}; } } @@ -656,6 +665,11 @@ public: * void(Owned &..., Get &...); * @endcode * + * @note + * Empty types aren't explicitly instantiated. Therefore, temporary objects + * are returned during iterations. They can be caught only by copy or with + * const references. + * * @tparam Func Type of the function object to invoke. * @param func A valid function object. */ @@ -665,7 +679,7 @@ public: [[maybe_unused]] auto data = std::get<0>(pools)->sparse_set::end() - *length; for(auto next = *length; next; --next) { - if constexpr(std::is_invocable_v..., std::add_lvalue_reference_t...>) { + if constexpr(std::is_invocable_v({}))..., decltype(get({}))...>) { if constexpr(sizeof...(Get) == 0) { func(*(std::get>(raw)++)...); } else { @@ -747,8 +761,7 @@ public: while(curr != next) { const auto lhs = copy[curr]; const auto rhs = copy[next]; - (std::swap(std::get *>(pools)->raw()[lhs], std::get *>(pools)->raw()[rhs]), ...); - (std::get *>(pools)->swap(lhs, rhs), ...); + (swap(0, std::get *>(pools), lhs, rhs), ...); copy[curr] = curr; curr = next; next = copy[curr]; diff --git a/src/entt/entity/registry.hpp b/src/entt/entity/registry.hpp index 02ebfa705..c73b96599 100644 --- a/src/entt/entity/registry.hpp +++ b/src/entt/entity/registry.hpp @@ -564,8 +564,7 @@ public: * just created otherwise. */ template - std::conditional_t> - create() { + decltype(auto) create() { entity_type entity; if(available) { @@ -584,7 +583,7 @@ public: if constexpr(sizeof...(Component) == 0) { return entity; } else { - return { entity, assign(entity)... }; + return std::tuple(entity))...>{entity, assign(entity)...}; } } @@ -602,8 +601,7 @@ public: * sorted the same of the entities otherwise. */ template - std::conditional_t> - create(It first, It last) { + auto create(It first, It last) { static_assert(std::is_convertible_v::value_type>); const auto length = size_type(std::distance(first, last)); const auto sz = std::min(available, length); @@ -630,7 +628,7 @@ public: }); if constexpr(sizeof...(Component) > 0) { - return { assure()->batch(*this, first, last)... }; + return std::make_tuple(assure()->batch(*this, first, last)...); } } @@ -783,17 +781,19 @@ public: if constexpr(sizeof...(Component) == 1) { return (pool()->get(entity), ...); } else { - return std::tuple &...>{get(entity)...}; + return std::tuple(entity))...>{get(entity)...}; } } /*! @copydoc get */ template inline decltype(auto) get([[maybe_unused]] const entity_type entity) ENTT_NOEXCEPT { + ENTT_ASSERT(valid(entity)); + if constexpr(sizeof...(Component) == 1) { - return (const_cast(std::as_const(*this).template get(entity)), ...); + return (pool()->get(entity), ...); } else { - return std::tuple{get(entity)...}; + return std::tuple(entity))...>{get(entity)...}; } } @@ -825,8 +825,7 @@ public: decltype(auto) get_or_assign(const entity_type entity, Args &&... args) ENTT_NOEXCEPT { ENTT_ASSERT(valid(entity)); auto *cpool = assure(); - auto *comp = cpool->try_get(entity); - return comp ? *comp : cpool->assign(*this, entity, std::forward(args)...); + return cpool->has(entity) ? cpool->get(entity) : cpool->assign(*this, entity, std::forward(args)...); } /** diff --git a/src/entt/entity/storage.hpp b/src/entt/entity/storage.hpp index d94b3eb07..45c180cdc 100644 --- a/src/entt/entity/storage.hpp +++ b/src/entt/entity/storage.hpp @@ -620,19 +620,6 @@ public: /*! @brief Random access iterator type. */ using iterator_type = iterator; - /** - * @brief Direct access to the array of objects. - * - * @note - * Empty types aren't explicitly instantiated. Therefore, this function - * always returns a null pointer. - * - * @return A pointer to the array of objects. - */ - const object_type * raw() const ENTT_NOEXCEPT { - return nullptr; - } - /** * @brief Returns an iterator to the beginning. * @@ -698,21 +685,6 @@ public: ENTT_ASSERT(underlying_type::has(entt)); return {}; } - - /** - * @brief Returns a pointer to the object associated with an entity, if any. - * - * @note - * Empty types aren't explicitly instantiated. Therefore, this function - * always returns a null pointer. - * - * @param entt A valid entity identifier. - * @return The object associated with the entity, if any. - */ - const object_type * try_get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(underlying_type::has(entt)); - return nullptr; - } }; /*! @copydoc basic_storage */ diff --git a/src/entt/entity/view.hpp b/src/entt/entity/view.hpp index 77817a0e1..2c838cb5c 100644 --- a/src/entt/entity/view.hpp +++ b/src/entt/entity/view.hpp @@ -179,7 +179,7 @@ class basic_view { auto curr = raw++; if((std::get *>(pools)->has(entity) && ...)) { - if constexpr(std::is_invocable_v...>) { + if constexpr(std::is_invocable_v({}))...>) { func(get(curr, std::get *>(pools), entity)...); } else { func(entity, get(curr, std::get *>(pools), entity)...); @@ -244,11 +244,6 @@ public: * 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. */ @@ -354,14 +349,13 @@ public: * @return The components assigned to the entity. */ template - std::conditional_t>, std::tuple> - get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { + decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { ENTT_ASSERT(contains(entt)); if constexpr(sizeof...(Comp) == 1) { return (std::get *>(pools)->get(entt), ...); } else { - return std::tuple{get(entt)...}; + return std::tuple(entt))...>{get(entt)...}; } } @@ -380,6 +374,11 @@ public: * void(Component &...); * @endcode * + * @note + * Empty types aren't explicitly instantiated. Therefore, temporary objects + * are returned during iterations. They can be caught only by copy or with + * const references. + * * @tparam Func Type of the function object to invoke. * @param func A valid function object. */ @@ -410,13 +409,19 @@ public: * It is no longer guaranteed that the performance is the best possible, but * there will be greater control over the order of iteration. * + * @note + * Empty types aren't explicitly instantiated. Therefore, temporary objects + * are returned during iterations. They can be caught only by copy or with + * const references. + * * @tparam Comp Type of component to use to enforce the iteration order. * @tparam Func Type of the function object to invoke. * @param func A valid function object. */ template inline void each(Func func) const { - each(type_list_cat_t, type_list<>, type_list>...>{}, std::move(func)); + using other_type = type_list_cat_t, type_list<>, type_list>...>; + each(other_type{}, std::move(func)); } private: @@ -470,7 +475,7 @@ class basic_view { public: /*! @brief Type of component iterated by the view. */ - using raw_type = std::remove_reference_t().get(0))>; + using raw_type = Component; /*! @brief Underlying entity identifier. */ using entity_type = typename pool_type::entity_type; /*! @brief Unsigned integer type. */ @@ -504,11 +509,6 @@ public: * 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. - * * @return A pointer to the array of components. */ raw_type * raw() const ENTT_NOEXCEPT { @@ -612,7 +612,7 @@ public: * @param entt A valid entity identifier. * @return The component assigned to the entity. */ - raw_type & get(const entity_type entt) const ENTT_NOEXCEPT { + decltype(auto) get(const entity_type entt) const ENTT_NOEXCEPT { ENTT_ASSERT(contains(entt)); return pool->get(entt); } @@ -632,12 +632,17 @@ public: * void(Component &); * @endcode * + * @note + * Empty types aren't explicitly instantiated. Therefore, temporary objects + * are returned during iterations. They can be caught only by copy or with + * const references. + * * @tparam Func Type of the function object to invoke. * @param func A valid function object. */ template void each(Func func) const { - if constexpr(std::is_invocable_v>) { + if constexpr(std::is_invocable_v) { std::for_each(pool->begin(), pool->end(), std::move(func)); } else { std::for_each(pool->sparse_set::begin(), pool->sparse_set::end(), [&func, raw = pool->begin()](const auto entt) mutable { diff --git a/test/entt/entity/group.cpp b/test/entt/entity/group.cpp index 45056395a..e803e0564 100644 --- a/test/entt/entity/group.cpp +++ b/test/entt/entity/group.cpp @@ -242,10 +242,8 @@ TEST(NonOwningGroup, ConstNonConstAndAllInBetween) { ASSERT_EQ(group.size(), decltype(group.size()){1}); ASSERT_TRUE((std::is_same_v(0)), int &>)); - ASSERT_TRUE((std::is_same_v(0)), const int &>)); ASSERT_TRUE((std::is_same_v(0)), const char &>)); ASSERT_TRUE((std::is_same_v(0)), std::tuple>)); - ASSERT_TRUE((std::is_same_v(0)), std::tuple>)); ASSERT_TRUE((std::is_same_v()), const char *>)); ASSERT_TRUE((std::is_same_v()), int *>)); @@ -740,13 +738,10 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) { ASSERT_EQ(group.size(), decltype(group.size()){1}); ASSERT_TRUE((std::is_same_v(0)), int &>)); - ASSERT_TRUE((std::is_same_v(0)), const int &>)); ASSERT_TRUE((std::is_same_v(0)), const char &>)); ASSERT_TRUE((std::is_same_v(0)), double &>)); - ASSERT_TRUE((std::is_same_v(0)), const double &>)); ASSERT_TRUE((std::is_same_v(0)), const float &>)); ASSERT_TRUE((std::is_same_v(0)), std::tuple>)); - ASSERT_TRUE((std::is_same_v(0)), std::tuple>)); ASSERT_TRUE((std::is_same_v()), const float *>)); ASSERT_TRUE((std::is_same_v()), double *>)); ASSERT_TRUE((std::is_same_v()), const char *>)); diff --git a/test/entt/entity/registry.cpp b/test/entt/entity/registry.cpp index 58207f1e5..1ac773f27 100644 --- a/test/entt/entity/registry.cpp +++ b/test/entt/entity/registry.cpp @@ -1002,7 +1002,7 @@ TEST(Registry, CreateManyEntitiesAtOnce) { TEST(Registry, CreateAnEntityWithComponents) { entt::registry registry; - const auto &[entity, ivalue, cvalue] = registry.create(); + auto &&[entity, ivalue, cvalue] = registry.create(); ASSERT_FALSE(registry.empty()); ASSERT_FALSE(registry.empty()); diff --git a/test/entt/entity/storage.cpp b/test/entt/entity/storage.cpp index d2497c880..4b60627b1 100644 --- a/test/entt/entity/storage.cpp +++ b/test/entt/entity/storage.cpp @@ -87,8 +87,6 @@ TEST(Storage, EmptyType) { ASSERT_TRUE(set.has(42)); ASSERT_TRUE(set.has(99)); - ASSERT_EQ(set.try_get(42), nullptr); - ASSERT_EQ(std::as_const(set).try_get(42), std::as_const(set).try_get(99)); auto &&component = set.get(42); @@ -314,15 +312,6 @@ TEST(Storage, Raw) { ASSERT_EQ(*(set.raw() + 2u), 9); } -TEST(Storage, RawEmptyType) { - entt::storage set; - - set.construct(3); - - ASSERT_EQ(set.raw(), std::as_const(set).raw()); - ASSERT_EQ(set.try_get(3), set.raw()); -} - TEST(Storage, SortOrdered) { entt::storage set; diff --git a/test/entt/entity/view.cpp b/test/entt/entity/view.cpp index b4770a385..9929c5e4b 100644 --- a/test/entt/entity/view.cpp +++ b/test/entt/entity/view.cpp @@ -398,10 +398,8 @@ TEST(MultipleComponentView, ConstNonConstAndAllInBetween) { ASSERT_EQ(view.size(), decltype(view.size()){1}); ASSERT_TRUE((std::is_same_v(0)), int &>)); - ASSERT_TRUE((std::is_same_v(0)), const int &>)); ASSERT_TRUE((std::is_same_v(0)), const char &>)); ASSERT_TRUE((std::is_same_v(0)), std::tuple>)); - ASSERT_TRUE((std::is_same_v(0)), std::tuple>)); ASSERT_TRUE((std::is_same_v()), const char *>)); ASSERT_TRUE((std::is_same_v()), int *>));