diff --git a/README.md b/README.md index cd8a62659..6fa9f65f7 100644 --- a/README.md +++ b/README.md @@ -168,8 +168,8 @@ Dell XPS 13 out of the mid 2014): | Standard view, 10M entities, ten components
Half of the entities have all the components | **0.0090s** | 0.0620s | | Standard view, 10M entities, ten components
One of the entities has all the components | 0.0070s | **1.3e-06s** | | Persistent view, 10M entities, ten components | 0.0105s | **6.2e-07s** | -| Sort 150k entities, one component | - | **0.0084s** | -| Sort 150k entities, enforce permutation | - | **0.0067s** | +| Sort 150k entities, one component
Arrays are in reverse order | - | **0.0043s** | +| Sort 150k entities, enforce permutation
Arrays are in reverse order | - | **0.0006s** | `EnTT` includes its own tests and benchmarks. See [benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp) diff --git a/src/entt/entity/registry.hpp b/src/entt/entity/registry.hpp index 5d549ef71..c6840c08f 100644 --- a/src/entt/entity/registry.hpp +++ b/src/entt/entity/registry.hpp @@ -694,11 +694,7 @@ public: */ template void sort(Compare compare) { - auto &cpool = ensure(); - - cpool.sort([&cpool, compare = std::move(compare)](auto lhs, auto rhs) { - return compare(static_cast(cpool.get(lhs)), static_cast(cpool.get(rhs))); - }); + ensure().sort(std::move(compare)); } /** diff --git a/src/entt/entity/sparse_set.hpp b/src/entt/entity/sparse_set.hpp index 7cd1f19ce..85422d60c 100644 --- a/src/entt/entity/sparse_set.hpp +++ b/src/entt/entity/sparse_set.hpp @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -281,50 +282,18 @@ public: * An assertion will abort the execution at runtime in debug mode if the * sparse set doesn't contain the given entities. * - * @param lhs A valid entity identifier. - * @param rhs A valid entity identifier. + * @param lhs A valid position within the sparse set. + * @param rhs A valid position within the sparse set. */ - virtual void swap(entity_type lhs, entity_type rhs) { - assert(has(lhs)); - assert(has(rhs)); - auto &le = reverse[lhs & traits_type::entity_mask]; - auto &re = reverse[rhs & traits_type::entity_mask]; - // we must get rid of the in-use bit for it's not part of the position - std::swap(direct[le & ~in_use], direct[re & ~in_use]); - std::swap(le, re); + void swap(pos_type lhs, pos_type rhs) noexcept { + assert(lhs < direct.size()); + assert(rhs < direct.size()); + std::swap(reverse[direct[lhs]], reverse[direct[rhs]]); + std::swap(direct[lhs], direct[rhs]); } /** - * @brief Sort entities according to the given comparison function. - * - * Sort the elements so that iterating the sparse set with a couple of - * iterators returns them in the expected order. See `begin` and `end` for - * more details. - * - * @note - * Attempting to iterate elements using the raw pointer returned by `data` - * gives no guarantees on the order, even though `sort` has been invoked. - * - * @tparam Compare Type of the comparison function. - * @param compare A comparison function whose signature shall be equivalent - * to: `bool(Entity, Entity)`. - */ - template - void sort(Compare compare) { - std::vector copy{direct.cbegin(), direct.cend()}; - std::sort(copy.begin(), copy.end(), [compare = std::move(compare)](auto... args) { - return !compare(args...); - }); - - for(pos_type i = 0; i < copy.size(); ++i) { - if(direct[i] != copy[i]) { - swap(direct[i], copy[i]); - } - } - } - - /** - * @brief Sort entities according to their order in a sparse set. + * @brief Sort entities according to their order in another sparse set. * * Entities that are part of both the sparse sets are ordered internally * according to the order they have in `other`. All the other entities goes @@ -342,32 +311,23 @@ public: * * @param other The sparse sets that imposes the order of the entities. */ - void respect(const SparseSet &other) { - struct Bool { bool value{false}; }; - std::vector check(std::max(other.reverse.size(), reverse.size())); + virtual void respect(const SparseSet &other) noexcept { + auto from = other.begin(); + auto to = other.end(); - for(auto entity: other.direct) { - check[entity & traits_type::entity_mask].value = true; - } + pos_type pos = direct.size() - 1; - sort([this, &other, &check](auto lhs, auto rhs) { - const auto le = lhs & traits_type::entity_mask; - const auto re = rhs & traits_type::entity_mask; + while(pos > 0 && from != to) { + if(has(*from)) { + if(*from != direct[pos]) { + swap(pos, get(*from)); + } - const bool bLhs = check[le].value; - const bool bRhs = check[re].value; - bool compare = false; - - if(bLhs && bRhs) { - compare = other.get(rhs) < other.get(lhs); - } else if(!bLhs && !bRhs) { - compare = re < le; - } else { - compare = bLhs; + --pos; } - return compare; - }); + ++from; + } } /** @@ -546,24 +506,87 @@ public: } /** - * @brief Swaps two entities and their objects. + * @brief Sort components according to the given comparison function. + * + * Sort the elements so that iterating the sparse set with a couple of + * iterators returns them in the expected order. See `begin` and `end` for + * more details. * * @note - * This function doesn't swap objects between entities. It exchanges entity - * and object positions in the sparse set. It's used mainly for sorting. + * Attempting to iterate elements using the raw pointer returned by `data` + * gives no guarantees on the order, even though `sort` has been invoked. * - * @warning - * Attempting to use entities that don't belong to the sparse set results - * in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set doesn't contain the given entities. - * - * @param lhs A valid entity identifier. - * @param rhs A valid entity identifier. + * @tparam Compare Type of the comparison function. + * @param compare A comparison function whose signature shall be equivalent + * to: `bool(const Type &, const Type &)`. */ - void swap(entity_type lhs, entity_type rhs) override { - std::swap(instances[underlying_type::get(lhs)], instances[underlying_type::get(rhs)]); - underlying_type::swap(lhs, rhs); + template + void sort(Compare compare) { + std::vector copy(instances.size()); + std::iota(copy.begin(), copy.end(), 0); + + std::sort(copy.begin(), copy.end(), [this, compare = std::move(compare)](auto lhs, auto rhs) { + return compare(const_cast(instances[rhs]), const_cast(instances[lhs])); + }); + + for(pos_type i = 0; i < copy.size(); ++i) { + auto curr = i; + auto next = copy[curr]; + + while(curr != next) { + auto lhs = copy[curr]; + auto rhs = copy[next]; + std::swap(instances[lhs], instances[rhs]); + underlying_type::swap(lhs, rhs); + copy[curr] = curr; + curr = next; + next = copy[curr]; + } + } + } + + /** + * @brief Sort components according to the order of the entities in another + * sparse set. + * + * Entities that are part of both the sparse sets are ordered internally + * according to the order they have in `other`. All the other entities goes + * to the end of the list and there are no guarantess on their order. + * Components are sorted according to the entities to which they + * belong.
+ * In other terms, this function can be used to impose the same order on two + * sets by using one of them as a master and the other one as a slave. + * + * Iterating the sparse set with a couple of iterators returns elements in + * the expected order after a call to `sort`. See `begin` and `end` for more + * details. + * + * @note + * Attempting to iterate elements using the raw pointer returned by `data` + * gives no guarantees on the order, even though `sort` has been invoked. + * + * @param other The sparse sets that imposes the order of the entities. + */ + void respect(const SparseSet &other) noexcept override { + auto from = other.begin(); + auto to = other.end(); + + pos_type pos = underlying_type::size() - 1; + const auto *direct = underlying_type::data(); + + while(pos > 0 && from != to) { + if(underlying_type::has(*from)) { + if(*from != *(direct + pos)) { + auto candidate = underlying_type::get(*from); + std::swap(instances[pos], instances[candidate]); + underlying_type::swap(pos, candidate); + } + + --pos; + } + + ++from; + } } /** diff --git a/src/entt/signal/dispatcher.hpp b/src/entt/signal/dispatcher.hpp index 5eb0fc65b..b0aca97c2 100644 --- a/src/entt/signal/dispatcher.hpp +++ b/src/entt/signal/dispatcher.hpp @@ -40,7 +40,7 @@ class Dispatcher final { template struct SignalWrapper final: BaseSignalWrapper { - void publish(std::size_t current) final override { + void publish(std::size_t current) override { for(auto &&event: events[current]) { signal.publish(event); } diff --git a/test/entt/entity/sparse_set.cpp b/test/entt/entity/sparse_set.cpp index d68b1a588..a1c8d18c7 100644 --- a/test/entt/entity/sparse_set.cpp +++ b/test/entt/entity/sparse_set.cpp @@ -160,7 +160,7 @@ TEST(SparseSetWithType, SortOrdered) { ASSERT_EQ(set.get(9), 1); set.sort([&set](auto lhs, auto rhs) { - return set.get(lhs) < set.get(rhs); + return lhs < rhs; }); ASSERT_EQ(*(set.raw() + 0u), 12); @@ -196,7 +196,7 @@ TEST(SparseSetWithType, SortReverse) { ASSERT_EQ(set.get(9), 12); set.sort([&set](auto lhs, auto rhs) { - return set.get(lhs) < set.get(rhs); + return lhs < rhs; }); ASSERT_EQ(*(set.raw() + 0u), 12); @@ -232,7 +232,7 @@ TEST(SparseSetWithType, SortUnordered) { ASSERT_EQ(set.get(9), 12); set.sort([&set](auto lhs, auto rhs) { - return set.get(lhs) < set.get(rhs); + return lhs < rhs; }); ASSERT_EQ(*(set.raw() + 0u), 12);