Преглед изворни кода

group: drop nested groups support, prepare to the large group review and multi storage support

Michele Caini пре 3 година
родитељ
комит
bd34e7f2c7
5 измењених фајлова са 25 додато и 275 уклоњено
  1. 1 81
      docs/md/entity.md
  2. 9 24
      src/entt/entity/group.hpp
  3. 3 48
      src/entt/entity/registry.hpp
  4. 6 23
      test/entt/entity/group.cpp
  5. 6 99
      test/entt/entity/registry.cpp

+ 1 - 81
docs/md/entity.md

@@ -50,7 +50,6 @@
     * [Full-owning groups](#full-owning-groups)
     * [Partial-owning groups](#partial-owning-groups)
     * [Non-owning groups](#non-owning-groups)
-    * [Nested groups](#nested-groups)
   * [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between)
   * [Give me everything](#give-me-everything)
   * [What is allowed and what is not](#what-is-allowed-and-what-is-not)
@@ -1420,9 +1419,7 @@ groups_ and _non-owning groups_. The main difference between them is in terms of
 performance.<br/>
 Groups can literally _own_ one or more component types. They are allowed to
 rearrange pools so as to speed up iterations. Roughly speaking: the more
-components a group owns, the faster it is to iterate them.<br/>
-A given component can belong to multiple groups only if they are _nested_. Users
-have to define groups carefully to get the best out of them.
+components a group owns, the faster it is to iterate them.
 
 ## Views
 
@@ -1796,83 +1793,6 @@ the only one that can be used in any situation to slightly improve performance.
 Non-owning groups are sorted using their `sort` member functions. Sorting a
 non-owning group affects all its instances.
 
-### Nested groups
-
-A type of component cannot be owned by two or more conflicting groups such as:
-
-* `registry.group<transform, sprite>()`.
-* `registry.group<transform, rotation>()`.
-
-However, the same type can be owned by groups belonging to the same _family_,
-also called _nested groups_, such as:
-
-* `registry.group<sprite, transform>()`.
-* `registry.group<sprite, transform, rotation>()`.
-
-Fortunately, these are also very common cases if not the most common ones.<br/>
-It allows to increase performance on a greater number of component combinations.
-
-Two nested groups are such that they own at least one component type and the list
-of component types involved by one of them is contained entirely in that of the
-other. More specifically, this applies independently to all component lists used
-to define a group.<br/>
-Therefore, the rules for defining whether two or more groups are nested is:
-
-* One of the groups involves one or more additional component types with respect
-  to the other, whether they are owned, observed or excluded.
-
-* The list of component types owned by the most restrictive group is the same or
-  contains entirely that of the others. This also applies to the list of
-  observed and excluded components.
-
-This means that nested groups _extend_ their parents by adding more conditions
-in the form of new components.
-
-As mentioned, the components don't necessarily have to be all _owned_ so that
-two groups can be considered nested. The following definitions are fully valid:
-
-* `registry.group<sprite>(entt::get<renderable>)`.
-* `registry.group<sprite, transform>(entt::get<renderable>)`.
-* `registry.group<sprite, transform>(entt::get<renderable, rotation>)`.
-
-Exclusion lists also play their part in this respect. When it comes to defining
-nested groups, an excluded component type `T` is treated as being an observed
-type `not_T`. Therefore, consider these two definitions:
-
-* `registry.group<sprite, transform>()`.
-* `registry.group<sprite, transform>({}, entt::exclude<rotation>)`.
-
-They are treated as if users were defining the following groups:
-
-* `group<sprite, transform>()`.
-* `group<sprite, transform>(entt::get<not_rotation>)`.
-
-Where `not_rotation` is an empty tag present only when `rotation` is not.
-
-Because of this, to define a new group that is more restrictive than an existing
-one, it's enough to extend the component list of another group by adding new
-types that are either owned, observed or excluded.<br/>
-The opposite is also true. To define a _larger_ group, it's enough to remove
-_constraints_ from its parent.<br/>
-Note that the greater the number of component types involved by a group, the
-more restrictive it is.
-
-Despite the extreme flexibility of nested groups which allow to independently
-use component types either owned, observed or excluded, the real strength of
-this tool lies in the possibility of defining a greater number of groups that
-**own** the same components, thus offering the best performance in more
-cases.<br/>
-In fact, given a list of component types involved by a group, the greater the
-number of those owned, the greater the performance of the group itself.
-
-As a side note, it's no longer possible to sort all groups when defining nested
-ones. This is because the most restrictive group shares its elements with the
-less restrictive ones and ordering the latter would invalidate the former.<br/>
-However, given a family of nested groups, it's still possible to sort the most
-restrictive of them. To prevent users from having to remember which of their
-groups is the most restrictive, the registry class offers the `sortable` member
-function to know if a group supports sorting it or not.
-
 ## Types: const, non-const and all in between
 
 The `registry` class offers two overloads when it comes to constructing views

+ 9 - 24
src/entt/entity/group.hpp

@@ -104,7 +104,7 @@ struct owning_group_descriptor {
     virtual void push_on_destroy(const entity_type) = 0;
     virtual void remove_if(const entity_type) = 0;
 
-    virtual size_type check(const id_type *, const size_type, const size_type, const size_type) const noexcept = 0;
+    virtual size_type owned(const id_type *, const size_type) const noexcept = 0;
     virtual size_type size() const noexcept = 0;
 };
 
@@ -144,17 +144,6 @@ class group_handler<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> fin
         }
     }
 
-    template<typename... Type>
-    auto check(const id_type *elem, const typename underlying_type::size_type len) const noexcept {
-        size_type cnt = 0u;
-
-        for(auto pos = 0u; pos < len; ++pos) {
-            cnt += ((elem[pos] == entt::type_hash<typename Type::value_type>::value()) || ...);
-        }
-
-        return cnt;
-    }
-
 public:
     using base_type = underlying_type;
     using size_type = typename base_type::size_type;
@@ -172,22 +161,18 @@ public:
         }
     }
 
-    size_type check(const id_type *elem, const size_type olen, const size_type glen, const size_type elen) const noexcept final {
-        return check<Owned...>(elem, olen) + check<Get...>(elem + olen, glen) + check<Exclude...>(elem + olen + glen, elen);
-    }
+    size_type owned(const id_type *elem, size_type len) const noexcept final {
+        size_type cnt = 0u;
 
-    size_type size() const noexcept final {
-        return sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude);
-    }
+        for(auto pos = 0u; pos < len; ++pos) {
+            cnt += ((elem[pos] == entt::type_hash<typename Owned::value_type>::value()) || ...);
+        }
 
-    void previous(const base_type &elem) {
-        std::apply([this, &elem](auto *...cpool) { ((cpool->on_destroy().disconnect(this), cpool->on_destroy().before(&elem).template connect<&group_handler::remove_if>(*this)), ...); }, pools);
-        std::apply([this, &elem](auto *...cpool) { ((cpool->on_construct().disconnect(this), cpool->on_construct().before(&elem).template connect<&group_handler::remove_if>(*this)), ...); }, filter);
+        return cnt;
     }
 
-    void next(const base_type &elem) {
-        std::apply([this, &elem](auto *...cpool) { ((cpool->on_construct().disconnect(this), cpool->on_construct().before(&elem).template connect<&group_handler::push_on_construct>(*this)), ...); }, pools);
-        std::apply([this, &elem](auto *...cpool) { ((cpool->on_destroy().disconnect(this), cpool->on_destroy().before(&elem).template connect<&group_handler::push_on_destroy>(*this)), ...); }, filter);
+    size_type size() const noexcept final {
+        return sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude);
     }
 
     [[nodiscard]] std::size_t length() const noexcept {

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

@@ -1222,40 +1222,10 @@ public:
                 return {*std::static_pointer_cast<handler_type>(it->second)};
             }
 
-            const id_type elem[]{type_hash<std::remove_const_t<Owned>>::value()..., type_hash<std::remove_const_t<Get>>::value()..., type_hash<std::remove_const_t<Exclude>>::value()...};
             auto handler = std::allocate_shared<handler_type>(get_allocator(), assure<std::remove_const_t<Owned>>()..., assure<std::remove_const_t<Get>>()..., assure<std::remove_const_t<Exclude>>()...);
+            [[maybe_unused]] const id_type elem[]{type_hash<std::remove_const_t<Owned>>::value()..., type_hash<std::remove_const_t<Get>>::value()..., type_hash<std::remove_const_t<Exclude>>::value()...};
+            ENTT_ASSERT(std::all_of(owning_groups.cbegin(), owning_groups.cend(), [&elem, hsize = handler->size()](const auto &data) { return data.second->owned(elem, sizeof...(Owned)) == 0u; }), "Conflicting groups");
             owning_groups.emplace(type_hash<handler_type>::value(), handler);
-
-            ENTT_ASSERT(std::all_of(owning_groups.cbegin(), owning_groups.cend(), [&elem, hsize = handler->size()](const auto &data) {
-                            const auto overlapping = data.second->check(elem, sizeof...(Owned), 0u, 0u);
-                            const auto sz = data.second->check(elem, sizeof...(Owned), sizeof...(Get), sizeof...(Exclude));
-                            return !overlapping || ((sz == hsize) || (sz == data.second->size()));
-                        }),
-                        "Conflicting groups");
-
-            const internal::owning_group_descriptor<base_type> *prev = nullptr;
-            const internal::owning_group_descriptor<base_type> *next = nullptr;
-
-            for(auto &&data: owning_groups) {
-                if(const auto sz = data.second->size(); data.second->check(elem, sizeof...(Owned), 0u, 0u)) {
-                    if(sz < handler->size() && (prev == nullptr || prev->size() < sz)) {
-                        prev = data.second.get();
-                    }
-
-                    if(sz > handler->size() && (next == nullptr || next->size() > sz)) {
-                        next = data.second.get();
-                    }
-                }
-            }
-
-            if(prev) {
-                handler->previous(*prev);
-            }
-
-            if(next) {
-                handler->next(*next);
-            }
-
             return {*handler};
         }
     }
@@ -1289,22 +1259,7 @@ public:
     template<typename Type, typename... Other>
     [[nodiscard]] bool owned() const {
         const id_type elem[]{type_hash<std::remove_const_t<Type>>::value(), type_hash<std::remove_const_t<Other>>::value()...};
-        return std::any_of(owning_groups.cbegin(), owning_groups.cend(), [&elem](auto &&data) { return data.second->check(elem, 1u + sizeof...(Other), 0u, 0u); });
-    }
-
-    /**
-     * @brief Checks whether a group can be sorted.
-     * @tparam Owned Type of storage _owned_ by the group.
-     * @tparam Get Type of storage _observed_ by the group.
-     * @tparam Exclude Type of storage used to filter the group.
-     * @return True if the group can be sorted, false otherwise.
-     */
-    template<typename... Owned, typename... Get, typename... Exclude>
-    [[nodiscard]] bool sortable(const basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> &) noexcept {
-        constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude);
-        const id_type elem[]{type_hash<typename Owned::value_type>::value()...};
-        auto pred = [&elem, size](const auto &data) { return data.second->check(elem, sizeof...(Owned), 0u, 0u) && (size < data.second->size()); };
-        return std::find_if(owning_groups.cbegin(), owning_groups.cend(), std::move(pred)) == owning_groups.cend();
+        return std::any_of(owning_groups.cbegin(), owning_groups.cend(), [&elem](auto &&data) { return data.second->owned(elem, 1u + sizeof...(Other)); });
     }
 
     /**

+ 6 - 23
test/entt/entity/group.cpp

@@ -8,6 +8,7 @@
 #include <gtest/gtest.h>
 #include <entt/entity/group.hpp>
 #include <entt/entity/registry.hpp>
+#include "../common/config.h"
 
 struct empty_type {};
 
@@ -1601,29 +1602,11 @@ TEST(OwningGroup, Storage) {
     ASSERT_FALSE((registry.any_of<int, double, float>(entity)));
 }
 
-TEST(OwningGroup, Overlapping) {
+ENTT_DEBUG_TEST(OwningGroup, Overlapping) {
     entt::registry registry;
+    registry.group<char>(entt::get<int>, entt::exclude<double>);
 
-    auto group = registry.group<char>(entt::get<int>, entt::exclude<double>);
-    auto other = registry.group<char>(entt::get<int, float>, entt::exclude<double>);
-
-    ASSERT_TRUE(group.empty());
-    ASSERT_TRUE(other.empty());
-
-    const auto entity = registry.create();
-    registry.emplace<char>(entity, '1');
-    registry.emplace<int>(entity, 42);
-
-    ASSERT_FALSE(group.empty());
-    ASSERT_TRUE(other.empty());
-
-    registry.emplace<float>(entity, 7.f);
-
-    ASSERT_FALSE(group.empty());
-    ASSERT_FALSE(other.empty());
-
-    registry.emplace<double>(entity, 3.);
-
-    ASSERT_TRUE(group.empty());
-    ASSERT_TRUE(other.empty());
+    ASSERT_DEATH((registry.group<char, float>(entt::get<float>, entt::exclude<double>)), "");
+    ASSERT_DEATH(registry.group<char>(entt::get<int, float>, entt::exclude<double>), "");
+    ASSERT_DEATH(registry.group<char>(entt::get<int>, entt::exclude<double, float>), "");
 }

+ 6 - 99
test/entt/entity/registry.cpp

@@ -1250,107 +1250,14 @@ TEST(Registry, CleanPartialOwningGroupViewAfterRemoveAndClear) {
     ASSERT_EQ(group.size(), 0u);
 }
 
-TEST(Registry, NestedGroups) {
+ENTT_DEBUG_TEST(Registry, NestedGroups) {
     entt::registry registry;
-    entt::entity entities[10];
+    registry.group<int, double>(entt::get<char>);
 
-    registry.create(std::begin(entities), std::end(entities));
-    registry.insert<int>(std::begin(entities), std::end(entities));
-    registry.insert<char>(std::begin(entities), std::end(entities));
-    const auto g1 = registry.group<int>(entt::get<char>, entt::exclude<double>);
-
-    ASSERT_TRUE(registry.sortable(g1));
-    ASSERT_EQ(g1.size(), 10u);
-
-    const auto g2 = registry.group<int>(entt::get<char>);
-
-    ASSERT_TRUE(registry.sortable(g1));
-    ASSERT_FALSE(registry.sortable(g2));
-    ASSERT_EQ(g1.size(), 10u);
-    ASSERT_EQ(g2.size(), 10u);
-
-    for(auto i = 0u; i < 5u; ++i) {
-        ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
-        ASSERT_TRUE(g1.contains(entities[i * 2]));
-        ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
-        ASSERT_TRUE(g2.contains(entities[i * 2]));
-        registry.emplace<double>(entities[i * 2]);
-    }
-
-    ASSERT_EQ(g1.size(), 5u);
-    ASSERT_EQ(g2.size(), 10u);
-
-    for(auto i = 0u; i < 5u; ++i) {
-        ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
-        ASSERT_FALSE(g1.contains(entities[i * 2]));
-        ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
-        ASSERT_TRUE(g2.contains(entities[i * 2]));
-        registry.erase<int>(entities[i * 2 + 1]);
-    }
-
-    ASSERT_EQ(g1.size(), 0u);
-    ASSERT_EQ(g2.size(), 5u);
-
-    const auto g3 = registry.group<int, float>(entt::get<char>, entt::exclude<double>);
-
-    ASSERT_FALSE(registry.sortable(g1));
-    ASSERT_FALSE(registry.sortable(g2));
-    ASSERT_TRUE(registry.sortable(g3));
-
-    ASSERT_EQ(g1.size(), 0u);
-    ASSERT_EQ(g2.size(), 5u);
-    ASSERT_EQ(g3.size(), 0u);
-
-    for(auto i = 0u; i < 5u; ++i) {
-        ASSERT_FALSE(g1.contains(entities[i * 2 + 1]));
-        ASSERT_FALSE(g1.contains(entities[i * 2]));
-        ASSERT_FALSE(g2.contains(entities[i * 2 + 1]));
-        ASSERT_TRUE(g2.contains(entities[i * 2]));
-        ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
-        ASSERT_FALSE(g3.contains(entities[i * 2]));
-        registry.emplace<int>(entities[i * 2 + 1]);
-    }
-
-    ASSERT_EQ(g1.size(), 5u);
-    ASSERT_EQ(g2.size(), 10u);
-    ASSERT_EQ(g3.size(), 0u);
-
-    for(auto i = 0u; i < 5u; ++i) {
-        ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
-        ASSERT_FALSE(g1.contains(entities[i * 2]));
-        ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
-        ASSERT_TRUE(g2.contains(entities[i * 2]));
-        ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
-        ASSERT_FALSE(g3.contains(entities[i * 2]));
-        registry.emplace<float>(entities[i * 2]);
-    }
-
-    ASSERT_EQ(g1.size(), 5u);
-    ASSERT_EQ(g2.size(), 10u);
-    ASSERT_EQ(g3.size(), 0u);
-
-    for(auto i = 0u; i < 5u; ++i) {
-        registry.erase<double>(entities[i * 2]);
-    }
-
-    ASSERT_EQ(g1.size(), 10u);
-    ASSERT_EQ(g2.size(), 10u);
-    ASSERT_EQ(g3.size(), 5u);
-
-    for(auto i = 0u; i < 5u; ++i) {
-        ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
-        ASSERT_TRUE(g1.contains(entities[i * 2]));
-        ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
-        ASSERT_TRUE(g2.contains(entities[i * 2]));
-        ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
-        ASSERT_TRUE(g3.contains(entities[i * 2]));
-        registry.erase<int>(entities[i * 2 + 1]);
-        registry.erase<int>(entities[i * 2]);
-    }
-
-    ASSERT_EQ(g1.size(), 0u);
-    ASSERT_EQ(g2.size(), 0u);
-    ASSERT_EQ(g3.size(), 0u);
+    ASSERT_DEATH(registry.group<int>(entt::get<char>), "");
+    ASSERT_DEATH(registry.group<int>(entt::get<char, double>), "");
+    ASSERT_DEATH(registry.group<int>(entt::get<char>, entt::exclude<double>), "");
+    ASSERT_DEATH((registry.group<int, double>()), "");
 }
 
 ENTT_DEBUG_TEST(Registry, ConflictingGroups) {