Просмотр исходного кода

review: instance-less pools for empty types

Michele Caini 6 лет назад
Родитель
Сommit
f3eaeb96f0

+ 3 - 0
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)

+ 13 - 17
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<T>` returns true. They are also
 the same types for which _empty base optimization_ (EBO) is possibile.<br/>
 `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.<br/>
-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.
-
-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.<br/>
-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.
+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.<br/>
+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.
+
+More in general, none of the features offered by the library is affected, but
+for the ones that require to return actual instances.
 
 # Multithreading
 

+ 1 - 1
src/entt/entity/actor.hpp

@@ -92,7 +92,7 @@ struct basic_actor {
      * @return A reference to the newly created component.
      */
     template<typename Component, typename... Args>
-    Component & assign(Args &&... args) {
+    decltype(auto) assign(Args &&... args) {
         return reg->template assign_or_replace<Component>(entt, std::forward<Args>(args)...);
     }
 

+ 36 - 23
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<typename... Component>
-    std::conditional_t<sizeof...(Component) == 1, std::tuple_element_t<0, std::tuple<Component &...>>, std::tuple<Component &...>>
-    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<pool_type<Component> *>(pools)->get(entt), ...);
         } else {
-            return std::tuple<Component &...>{get<Component>(entt)...};
+            return std::tuple<decltype(get<Component>(entt))...>{get<Component>(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<typename Func>
     inline void each(Func func) const {
         for(const auto entt: *handler) {
-            if constexpr(std::is_invocable_v<Func, std::add_lvalue_reference_t<Get>...>) {
+            if constexpr(std::is_invocable_v<Func, decltype(get<Get>({}))...>) {
                 func(std::get<pool_type<Get> *>(pools)->get(entt)...);
             } else {
                 func(entt, std::get<pool_type<Get> *>(pools)->get(entt)...);
@@ -422,14 +421,30 @@ class basic_group<Entity, get_t<Get...>, Owned...> {
     using component_iterator_type = decltype(std::declval<pool_type<Component>>().begin());
 
     template<typename Component>
-    const Component & from_index(const typename sparse_set<Entity>::size_type index) {
+    decltype(auto) from_index(const typename sparse_set<Entity>::size_type index) {
         if constexpr(std::disjunction_v<std::is_same<Component, Owned>...>) {
-            return std::get<pool_type<Component> *>(pools)->raw()[index];
+            if constexpr(std::is_empty_v<Component>) {
+                return std::get<pool_type<Component> *>(pools).get();
+            } else {
+                return std::as_const(*std::get<pool_type<Component> *>(pools)).raw()[index];
+            }
         } else {
-            return std::get<pool_type<Component> *>(pools)->get(data()[index]);
+            return std::as_const(*std::get<pool_type<Component> *>(pools)).get(data()[index]);
         }
     }
 
+    template<typename Component>
+    inline auto swap(int, pool_type<Component> *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<typename Component>
+    inline void swap(char, pool_type<Component> *cpool, const std::size_t lhs, const std::size_t rhs) {
+        cpool->swap(lhs, rhs);
+    }
+
     // we could use pool_type<Type> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug)
     basic_group(const typename basic_registry<Entity>::size_type *sz, storage<Entity, std::remove_const_t<Owned>> *... owned, storage<Entity, std::remove_const_t<Get>> *... 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<typename... Component>
-    std::conditional_t<sizeof...(Component) == 1, std::tuple_element_t<0, std::tuple<Component &...>>, std::tuple<Component &...>>
-    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<pool_type<Component> *>(pools)->get(entt), ...);
         } else {
-            return std::tuple<Component &...>{get<Component>(entt)...};
+            return std::tuple<decltype(get<Component>(entt))...>{get<Component>(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<entity_type>::end() - *length;
 
         for(auto next = *length; next; --next) {
-            if constexpr(std::is_invocable_v<Func, std::add_lvalue_reference_t<Owned>..., std::add_lvalue_reference_t<Get>...>) {
+            if constexpr(std::is_invocable_v<Func, decltype(get<Owned>({}))..., decltype(get<Get>({}))...>) {
                 if constexpr(sizeof...(Get) == 0) {
                     func(*(std::get<component_iterator_type<Owned>>(raw)++)...);
                 } else {
@@ -747,8 +761,7 @@ public:
             while(curr != next) {
                 const auto lhs = copy[curr];
                 const auto rhs = copy[next];
-                (std::swap(std::get<pool_type<Owned> *>(pools)->raw()[lhs], std::get<pool_type<Owned> *>(pools)->raw()[rhs]), ...);
-                (std::get<pool_type<Owned> *>(pools)->swap(lhs, rhs), ...);
+                (swap<Owned>(0, std::get<pool_type<Owned> *>(pools), lhs, rhs), ...);
                 copy[curr] = curr;
                 curr = next;
                 next = copy[curr];

+ 10 - 11
src/entt/entity/registry.hpp

@@ -564,8 +564,7 @@ public:
      * just created otherwise.
      */
     template<typename... Component>
-    std::conditional_t<sizeof...(Component) == 0, entity_type, std::tuple<entity_type, Component &...>>
-    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<Component>(entity)... };
+            return std::tuple<entity_type, decltype(assign<Component>(entity))...>{entity, assign<Component>(entity)...};
         }
     }
 
@@ -602,8 +601,7 @@ public:
      * sorted the same of the entities otherwise.
      */
     template<typename... Component, typename It>
-    std::conditional_t<sizeof...(Component) == 0, void, std::tuple<Component *...>>
-    create(It first, It last) {
+    auto create(It first, It last) {
         static_assert(std::is_convertible_v<entity_type, typename std::iterator_traits<It>::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<Component>()->batch(*this, first, last)... };
+            return std::make_tuple(assure<Component>()->batch(*this, first, last)...);
         }
     }
 
@@ -783,17 +781,19 @@ public:
         if constexpr(sizeof...(Component) == 1) {
             return (pool<Component>()->get(entity), ...);
         } else {
-            return std::tuple<std::add_const_t<Component> &...>{get<Component>(entity)...};
+            return std::tuple<decltype(get<Component>(entity))...>{get<Component>(entity)...};
         }
     }
 
     /*! @copydoc get */
     template<typename... Component>
     inline decltype(auto) get([[maybe_unused]] const entity_type entity) ENTT_NOEXCEPT {
+        ENTT_ASSERT(valid(entity));
+
         if constexpr(sizeof...(Component) == 1) {
-            return (const_cast<Component &>(std::as_const(*this).template get<Component>(entity)), ...);
+            return (pool<Component>()->get(entity), ...);
         } else {
-            return std::tuple<Component &...>{get<Component>(entity)...};
+            return std::tuple<decltype(get<Component>(entity))...>{get<Component>(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<Component>();
-        auto *comp = cpool->try_get(entity);
-        return comp ? *comp : cpool->assign(*this, entity, std::forward<Args>(args)...);
+        return cpool->has(entity) ? cpool->get(entity) : cpool->assign(*this, entity, std::forward<Args>(args)...);
     }
 
     /**

+ 0 - 28
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 */

+ 23 - 18
src/entt/entity/view.hpp

@@ -179,7 +179,7 @@ class basic_view {
             auto curr = raw++;
 
             if((std::get<pool_type<Other> *>(pools)->has(entity) && ...)) {
-                if constexpr(std::is_invocable_v<Func, std::add_lvalue_reference_t<Component>...>) {
+                if constexpr(std::is_invocable_v<Func, decltype(get<Component>({}))...>) {
                     func(get<Comp, Component>(curr, std::get<pool_type<Component> *>(pools), entity)...);
                 } else {
                     func(entity, get<Comp, Component>(curr, std::get<pool_type<Component> *>(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<typename... Comp>
-    std::conditional_t<sizeof...(Comp) == 1, std::tuple_element_t<0, std::tuple<Comp &...>>, std::tuple<Comp &...>>
-    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<pool_type<Comp> *>(pools)->get(entt), ...);
         } else {
-            return std::tuple<Comp &...>{get<Comp>(entt)...};
+            return std::tuple<decltype(get<Comp>(entt))...>{get<Comp>(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<typename Comp, typename Func>
     inline void each(Func func) const {
-        each<Comp>(type_list_cat_t<std::conditional_t<std::is_same_v<Comp, Component>, type_list<>, type_list<Component>>...>{}, std::move(func));
+        using other_type = type_list_cat_t<std::conditional_t<std::is_same_v<Comp, Component>, type_list<>, type_list<Component>>...>;
+        each<Comp>(other_type{}, std::move(func));
     }
 
 private:
@@ -470,7 +475,7 @@ class basic_view<Entity, Component> {
 
 public:
     /*! @brief Type of component iterated by the view. */
-    using raw_type = std::remove_reference_t<decltype(std::declval<pool_type>().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<typename Func>
     void each(Func func) const {
-        if constexpr(std::is_invocable_v<Func, std::add_lvalue_reference_t<Component>>) {
+        if constexpr(std::is_invocable_v<Func, decltype(get({}))>) {
             std::for_each(pool->begin(), pool->end(), std::move(func));
         } else {
             std::for_each(pool->sparse_set<Entity>::begin(), pool->sparse_set<Entity>::end(), [&func, raw = pool->begin()](const auto entt) mutable {

+ 0 - 5
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<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<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 *>));
 
@@ -740,13 +738,10 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
     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 *>));

+ 1 - 1
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<int, char>();
+    auto &&[entity, ivalue, cvalue] = registry.create<int, char>();
 
     ASSERT_FALSE(registry.empty<int>());
     ASSERT_FALSE(registry.empty<char>());

+ 0 - 11
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<std::uint64_t, empty_type> 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<std::uint64_t, boxed_int> set;
 

+ 0 - 2
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<decltype(view.get<int>(0)), int &>));
-    ASSERT_TRUE((std::is_same_v<decltype(view.get<const int>(0)), const int &>));
     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 *>));