diff --git a/TODO b/TODO index cf44dd3a9..cdd59535c 100644 --- a/TODO +++ b/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 diff --git a/docs/md/entity.md b/docs/md/entity.md index 856bf387d..99044adb6 100644 --- a/docs/md/entity.md +++ b/docs/md/entity.md @@ -1160,8 +1160,11 @@ auto group = registry.group(entt::exclude); 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.
+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(entt::get, entt::exclude +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, entt::exclude); ``` -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 diff --git a/src/entt/entity/group.hpp b/src/entt/entity/group.hpp index 0418083c8..a1ebf93ac 100644 --- a/src/entt/entity/group.hpp +++ b/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 + void sort(Compare compare, Sort algo = Sort{}, Args &&... args) { + std::vector 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)...); + + 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 *>(pools)->raw()[lhs], std::get *>(pools)->raw()[rhs]), ...); + (std::get *>(pools)->swap(lhs, rhs), ...); + copy[curr] = curr; + curr = next; + next = copy[curr]; + } + } + } + private: const typename basic_registry::size_type *length; const std::tuple *..., pool_type *...> pools; diff --git a/test/entt/entity/group.cpp b/test/entt/entity/group.cpp index cb811ad7c..abac1f48d 100644 --- a/test/entt/entity/group.cpp +++ b/test/entt/entity/group.cpp @@ -5,6 +5,9 @@ #include #include +struct empty_type {}; +struct boxed_int { int value; }; + TEST(NonOwningGroup, Functionalities) { entt::registry registry; auto group = registry.group<>(entt::get); @@ -346,7 +349,6 @@ TEST(NonOwningGroup, ExcludedComponents) { } TEST(NonOwningGroup, EmptyAndNonEmptyTypes) { - struct empty_type {}; entt::registry registry; const auto group = registry.group<>(entt::get); @@ -547,6 +549,155 @@ TEST(OwningGroup, Each) { ASSERT_EQ(cnt, std::size_t{0}); } +TEST(OwningGroup, SortOrdered) { + entt::registry registry; + auto group = registry.group(); + + entt::entity entities[5] = { + registry.create(), + registry.create(), + registry.create(), + registry.create(), + registry.create() + }; + + registry.assign(entities[0], 12); + registry.assign(entities[0], 'a'); + + registry.assign(entities[1], 9); + registry.assign(entities[1], 'b'); + + registry.assign(entities[2], 6); + registry.assign(entities[2], 'c'); + + registry.assign(entities[3], 1); + registry.assign(entities[4], 2); + + group.sort([&group](const auto lhs, const auto rhs) { + 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() + 3u), entities[3]); + ASSERT_EQ(*(group.data() + 4u), entities[4]); + + ASSERT_EQ((group.raw() + 0u)->value, 12); + ASSERT_EQ((group.raw() + 1u)->value, 9); + ASSERT_EQ((group.raw() + 2u)->value, 6); + ASSERT_EQ((group.raw() + 3u)->value, 1); + ASSERT_EQ((group.raw() + 4u)->value, 2); + + ASSERT_EQ(*(group.raw() + 0u), 'a'); + ASSERT_EQ(*(group.raw() + 1u), 'b'); + ASSERT_EQ(*(group.raw() + 2u), 'c'); +} + +TEST(OwningGroup, SortReverse) { + entt::registry registry; + auto group = registry.group(); + + entt::entity entities[5] = { + registry.create(), + registry.create(), + registry.create(), + registry.create(), + registry.create() + }; + + registry.assign(entities[0], 6); + registry.assign(entities[0], 'a'); + + registry.assign(entities[1], 9); + registry.assign(entities[1], 'b'); + + registry.assign(entities[2], 12); + registry.assign(entities[2], 'c'); + + registry.assign(entities[3], 1); + registry.assign(entities[4], 2); + + group.sort([&group](const auto lhs, const auto rhs) { + return group.get(lhs).value < group.get(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() + 0u)->value, 12); + ASSERT_EQ((group.raw() + 1u)->value, 9); + ASSERT_EQ((group.raw() + 2u)->value, 6); + ASSERT_EQ((group.raw() + 3u)->value, 1); + ASSERT_EQ((group.raw() + 4u)->value, 2); + + ASSERT_EQ(*(group.raw() + 0u), 'c'); + ASSERT_EQ(*(group.raw() + 1u), 'b'); + ASSERT_EQ(*(group.raw() + 2u), 'a'); +} + +TEST(OwningGroup, SortUnordered) { + entt::registry registry; + auto group = registry.group(); + + entt::entity entities[7] = { + registry.create(), + registry.create(), + registry.create(), + registry.create(), + registry.create(), + registry.create(), + registry.create() + }; + + registry.assign(entities[0], 6); + registry.assign(entities[0], 'a'); + + registry.assign(entities[1], 3); + registry.assign(entities[1], 'b'); + + registry.assign(entities[2], 1); + registry.assign(entities[2], 'c'); + + registry.assign(entities[3], 9); + registry.assign(entities[3], 'd'); + + registry.assign(entities[4], 12); + registry.assign(entities[4], 'e'); + + registry.assign(entities[5], 4); + registry.assign(entities[6], 5); + + group.sort([&group](const auto lhs, const auto rhs) { + return group.get(lhs).value < group.get(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() + 0u)->value, 12); + ASSERT_EQ((group.raw() + 1u)->value, 9); + ASSERT_EQ((group.raw() + 2u)->value, 6); + ASSERT_EQ((group.raw() + 3u)->value, 3); + ASSERT_EQ((group.raw() + 4u)->value, 1); + ASSERT_EQ((group.raw() + 5u)->value, 4); + ASSERT_EQ((group.raw() + 6u)->value, 5); + + ASSERT_EQ(*(group.raw() + 0u), 'e'); + ASSERT_EQ(*(group.raw() + 1u), 'd'); + ASSERT_EQ(*(group.raw() + 2u), 'a'); + ASSERT_EQ(*(group.raw() + 3u), 'b'); + ASSERT_EQ(*(group.raw() + 4u), 'c'); +} + TEST(OwningGroup, IndexRebuiltOnDestroy) { entt::registry registry; auto group = registry.group(entt::get); @@ -704,7 +855,6 @@ TEST(OwningGroup, ExcludedComponents) { } TEST(OwningGroup, EmptyAndNonEmptyTypes) { - struct empty_type {}; entt::registry registry; const auto group = registry.group(entt::get);