Bläddra i källkod

made the groups sortable

Michele Caini 7 år sedan
förälder
incheckning
9bc015d10e
4 ändrade filer med 233 tillägg och 9 borttagningar
  1. 0 2
      TODO
  2. 15 5
      docs/md/entity.md
  3. 66 0
      src/entt/entity/group.hpp
  4. 152 2
      test/entt/entity/group.cpp

+ 0 - 2
TODO

@@ -8,8 +8,6 @@
 * meta: sort of meta view based on meta stuff to iterate entities, void * and meta info objects
 * allow for built-in parallel each if possible
 * allow to replace std:: with custom implementations
-* allow to sort groups (::respect can already work with begin/end instead of a whole sparse set)
-  -it would ease by far the group trick for hierarchies that requires otherwise more boilerplate
 * remove runtime views, welcome reflection and what about snapshot?
 * empty components model allows for shared components and prefabs unity-like
   - each with entity return the shared component multiple times, one per entity that refers to it

+ 15 - 5
docs/md/entity.md

@@ -1160,8 +1160,11 @@ auto group = registry.group<position, velocity>(entt::exclude<renderable>);
 
 Once created, the group gets the ownership of all the components specified in
 the template parameter list and arranges their pools so as to iterate all of
-them as fast as possible.<br/>
+them as fast as possible.
+
 Sorting owned components is no longer allowed once the group has been created.
+However, full-owning groups can be sorted by means of the `sort` member
+function, if required.
 
 ### Partial-owning groups
 
@@ -1186,8 +1189,11 @@ auto group = registry.group<position>(entt::get<velocity>, entt::exclude<rendera
 Once created, the group gets the ownership of all the components specified in
 the template parameter list and arranges their pools so as to iterate all of
 them as fast as possible. The ownership of the types provided via `entt::get`
-doesn't pass to the group instead.<br/>
+doesn't pass to the group instead.
+
 Sorting owned components is no longer allowed once the group has been created.
+However, partial-owning groups can be sorted by means of the `sort` member
+function, if required.
 
 ### Non-owning groups
 
@@ -1208,9 +1214,13 @@ Filtering entities by components is also supported:
 auto group = registry.group<>(entt::get<position, velocity>, entt::exclude<renderable>);
 ```
 
-The group doesn't receive the ownership of any type of component in this case.
-This has a positive implication, that is, the fact that non-owning groups can be
-sorted by means of the `sort` member function, if required.
+The group doesn't receive the ownership of any type of component in this
+case. This type of groups is therefore the least performing in general, but also
+the only one that can be used in any situation to improve a performance where
+necessary.
+
+Non-owning groups can be sorted by means of the `sort` member function, if
+required.
 
 # Types: const, non-const and all in between
 

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

@@ -666,6 +666,72 @@ public:
         }
     }
 
+    /**
+     * @brief Sort a group according to the given comparison function.
+     *
+     * Sort the group so that iterating it with a couple of iterators returns
+     * entities and components in the expected order. See `begin` and `end` for
+     * more details.
+     *
+     * The comparison function object must return `true` if the first element
+     * is _less_ than the second one, `false` otherwise. The signature of the
+     * comparison function should be equivalent to the following:
+     *
+     * @code{.cpp}
+     * bool(const Entity, const Entity);
+     * @endcode
+     *
+     * Moreover, the comparison function object shall induce a
+     * _strict weak ordering_ on the values.
+     *
+     * The sort function oject must offer a member function template
+     * `operator()` that accepts three arguments:
+     *
+     * * An iterator to the first element of the range to sort.
+     * * An iterator past the last element of the range to sort.
+     * * A comparison function to use to compare the elements.
+     *
+     * The comparison function object received by the sort function object
+     * hasn't necessarily the type of the one passed along with the other
+     * parameters to this member function.
+     *
+     * @note
+     * Attempting to iterate elements using a raw pointer returned by a call to
+     * either `data` or `raw` gives no guarantees on the order, even though
+     * `sort` has been invoked.
+     *
+     * @tparam Compare Type of comparison function object.
+     * @tparam Sort Type of sort function object.
+     * @tparam Args Types of arguments to forward to the sort function object.
+     * @param compare A valid comparison function object.
+     * @param algo A valid sort function object.
+     * @param args Arguments to forward to the sort function object, if any.
+     */
+    template<typename Compare, typename Sort = std_sort, typename... Args>
+    void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
+        std::vector<size_type> copy(*length);
+        std::iota(copy.begin(), copy.end(), 0);
+
+        algo(copy.rbegin(), copy.rend(), [this, compare = std::move(compare), data = data()](const auto lhs, const auto rhs) {
+            return compare(data[lhs], data[rhs]);
+        }, std::forward<Args>(args)...);
+
+        for(size_type pos = 0, last = copy.size(); pos < last; ++pos) {
+            auto curr = pos;
+            auto next = copy[curr];
+
+            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), ...);
+                copy[curr] = curr;
+                curr = next;
+                next = copy[curr];
+            }
+        }
+    }
+
 private:
     const typename basic_registry<Entity>::size_type *length;
     const std::tuple<pool_type<Owned> *..., pool_type<Get> *...> pools;

+ 152 - 2
test/entt/entity/group.cpp

@@ -5,6 +5,9 @@
 #include <entt/entity/registry.hpp>
 #include <entt/entity/group.hpp>
 
+struct empty_type {};
+struct boxed_int { int value; };
+
 TEST(NonOwningGroup, Functionalities) {
     entt::registry registry;
     auto group = registry.group<>(entt::get<int, char>);
@@ -346,7 +349,6 @@ TEST(NonOwningGroup, ExcludedComponents) {
 }
 
 TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
-    struct empty_type {};
     entt::registry registry;
     const auto group = registry.group<>(entt::get<int, empty_type>);
 
@@ -547,6 +549,155 @@ TEST(OwningGroup, Each) {
     ASSERT_EQ(cnt, std::size_t{0});
 }
 
+TEST(OwningGroup, SortOrdered) {
+    entt::registry registry;
+    auto group = registry.group<boxed_int, char>();
+
+    entt::entity entities[5] = {
+        registry.create(),
+        registry.create(),
+        registry.create(),
+        registry.create(),
+        registry.create()
+    };
+
+    registry.assign<boxed_int>(entities[0], 12);
+    registry.assign<char>(entities[0], 'a');
+
+    registry.assign<boxed_int>(entities[1], 9);
+    registry.assign<char>(entities[1], 'b');
+
+    registry.assign<boxed_int>(entities[2], 6);
+    registry.assign<char>(entities[2], 'c');
+
+    registry.assign<boxed_int>(entities[3], 1);
+    registry.assign<boxed_int>(entities[4], 2);
+
+    group.sort([&group](const auto lhs, const auto rhs) {
+        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() + 3u), entities[3]);
+    ASSERT_EQ(*(group.data() + 4u), entities[4]);
+
+    ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
+    ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
+    ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
+    ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1);
+    ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2);
+
+    ASSERT_EQ(*(group.raw<char>() + 0u), 'a');
+    ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
+    ASSERT_EQ(*(group.raw<char>() + 2u), 'c');
+}
+
+TEST(OwningGroup, SortReverse) {
+    entt::registry registry;
+    auto group = registry.group<boxed_int, char>();
+
+    entt::entity entities[5] = {
+        registry.create(),
+        registry.create(),
+        registry.create(),
+        registry.create(),
+        registry.create()
+    };
+
+    registry.assign<boxed_int>(entities[0], 6);
+    registry.assign<char>(entities[0], 'a');
+
+    registry.assign<boxed_int>(entities[1], 9);
+    registry.assign<char>(entities[1], 'b');
+
+    registry.assign<boxed_int>(entities[2], 12);
+    registry.assign<char>(entities[2], 'c');
+
+    registry.assign<boxed_int>(entities[3], 1);
+    registry.assign<boxed_int>(entities[4], 2);
+
+    group.sort([&group](const auto lhs, const auto rhs) {
+        return group.get<boxed_int>(lhs).value < group.get<boxed_int>(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() + 3u), entities[3]);
+    ASSERT_EQ(*(group.data() + 4u), entities[4]);
+
+    ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
+    ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
+    ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
+    ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1);
+    ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2);
+
+    ASSERT_EQ(*(group.raw<char>() + 0u), 'c');
+    ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
+    ASSERT_EQ(*(group.raw<char>() + 2u), 'a');
+}
+
+TEST(OwningGroup, SortUnordered) {
+    entt::registry registry;
+    auto group = registry.group<boxed_int, char>();
+
+    entt::entity entities[7] = {
+        registry.create(),
+        registry.create(),
+        registry.create(),
+        registry.create(),
+        registry.create(),
+        registry.create(),
+        registry.create()
+    };
+
+    registry.assign<boxed_int>(entities[0], 6);
+    registry.assign<char>(entities[0], 'a');
+
+    registry.assign<boxed_int>(entities[1], 3);
+    registry.assign<char>(entities[1], 'b');
+
+    registry.assign<boxed_int>(entities[2], 1);
+    registry.assign<char>(entities[2], 'c');
+
+    registry.assign<boxed_int>(entities[3], 9);
+    registry.assign<char>(entities[3], 'd');
+
+    registry.assign<boxed_int>(entities[4], 12);
+    registry.assign<char>(entities[4], 'e');
+
+    registry.assign<boxed_int>(entities[5], 4);
+    registry.assign<boxed_int>(entities[6], 5);
+
+    group.sort([&group](const auto lhs, const auto rhs) {
+        return group.get<boxed_int>(lhs).value < group.get<boxed_int>(rhs).value;
+    });
+
+    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)->value, 12);
+    ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
+    ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
+    ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 3);
+    ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 1);
+    ASSERT_EQ((group.raw<boxed_int>() + 5u)->value, 4);
+    ASSERT_EQ((group.raw<boxed_int>() + 6u)->value, 5);
+
+    ASSERT_EQ(*(group.raw<char>() + 0u), 'e');
+    ASSERT_EQ(*(group.raw<char>() + 1u), 'd');
+    ASSERT_EQ(*(group.raw<char>() + 2u), 'a');
+    ASSERT_EQ(*(group.raw<char>() + 3u), 'b');
+    ASSERT_EQ(*(group.raw<char>() + 4u), 'c');
+}
+
 TEST(OwningGroup, IndexRebuiltOnDestroy) {
     entt::registry registry;
     auto group = registry.group<int>(entt::get<unsigned int>);
@@ -704,7 +855,6 @@ TEST(OwningGroup, ExcludedComponents) {
 }
 
 TEST(OwningGroup, EmptyAndNonEmptyTypes) {
-    struct empty_type {};
     entt::registry registry;
     const auto group = registry.group<empty_type>(entt::get<int>);