Bläddra i källkod

sorting no longer requires allocations and is much faster

Michele Caini 6 år sedan
förälder
incheckning
800751cbe0
6 ändrade filer med 214 tillägg och 96 borttagningar
  1. 28 39
      README.md
  2. 0 1
      TODO
  3. 10 12
      src/entt/entity/group.hpp
  4. 61 16
      src/entt/entity/sparse_set.hpp
  5. 8 7
      src/entt/entity/storage.hpp
  6. 107 21
      test/entt/entity/sparse_set.cpp

+ 28 - 39
README.md

@@ -183,47 +183,36 @@ amazing set of features. And even more, of course.
 ## Performance
 ## Performance
 
 
 As it stands right now, `EnTT` is just fast enough for my requirements when
 As it stands right now, `EnTT` is just fast enough for my requirements when
-compared to my first choice (it was already amazingly fast actually).<br/>
-Below is a comparison between the two (both of them compiled with GCC 7.3.0 on a
-Dell XPS 13 from mid 2014):
-
-| Benchmark | EntityX (compile-time) | EnTT |
-|-----------|-------------|-------------|
-| Create 1M entities | 0.0147s | **0.0046s** |
-| Destroy 1M entities | 0.0053s | **0.0045s** |
-| 1M entities, one component | 0.0012s | **1.9e-07s** |
-| 1M entities, two components | 0.0012s | **3.8e-07s** |
-| 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
-| 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
-| 1M entities, five components | 0.0010s | **7.0e-07s** |
-| 1M entities, ten components | 0.0011s | **1.2e-06s** |
-| 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
-| 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
-| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** |
-| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** |
-| Sort 150k entities, one component<br/>Arrays are almost sorted, std::sort | - | **0.0035s** |
-| Sort 150k entities, one component<br/>Arrays are almost sorted, insertion sort | - | **0.0007s** |
-
-Note: The default version of `EntityX` (`master` branch) wasn't added to the
-comparison because it's already much slower than its compile-time counterpart.
-
-Pretty interesting results, aren't them? In fact, these benchmarks are the ones
-used by `EntityX` to show _how fast it is_. To be honest, they aren't so good
-and these results shouldn't be taken too seriously (indeed they are completely
-unrealistic).<br/>
-The proposed entity-component system is incredibly fast to iterate entities,
-this is a fact. The compiler can make a lot of optimizations because of how
-`EnTT` works, even more when components aren't used at all. This is exactly the
-case for these benchmarks. On the other hand, if we consider real world cases,
-`EnTT` is somewhere between a bit and much faster than the other solutions
-around when users also access the components and not just the entities, although
-it isn't as fast as reported by these benchmarks.<br/>
-This is why they are completely wrong and cannot be used to evaluate any of the
-entity-component-system libraries out there.
+compared to my first choice (it was already amazingly fast actually).
+
+For a long time, this file contained also some benchmarks to show how fast
+`EnTT` was. However, I got tired of updating them whenever there is an
+improvement. Furthermore, there are a lot of projects out there that use `EnTT`
+as a basis for comparison (this should already tell you a lot) and offer their
+own more or less ad hoc results to show how they perform well downhill and with
+the wind at their back.<br/>
+Many of these benchmarks are completely wrong and cannot be used to evaluate any
+of the existing libraries, many others are simply incomplete, good at omitting
+some information and using the wrong function to compare a given feature.
+Certainly there are also good ones but they age quickly if nobody updates them,
+especially when the library they are dealing with is actively developed.<br/>
+Do you really want to have useless numbers on yet another README file?
+
+If you are interested, you can compile the `benchmark` test in release mode (to
+enable compiler optimizations, otherwise it would make little sense) by setting
+the `BUILD_BENCHMARK` option to `ON`, then evaluate yourself whether you're
+satisfied with the results or not.
+
+The proposed entity-component system is incredibly fast to iterate entities and
+components, this is a fact. Some compilers make a lot of optimizations because
+of how `EnTT` works, even more when components aren't used at all. In general,
+if we consider real world cases, `EnTT` is somewhere between a bit and much
+faster than many of the other solutions around, although I couldn't check them
+all for obvious reasons.
 
 
 The choice to use `EnTT` should be based on its carefully designed API, its
 The choice to use `EnTT` should be based on its carefully designed API, its
-set of features and the general performance, not because some single benchmark
-shows it to be the fastest tool available.
+set of features and the general performance, **not** because some single
+benchmark shows it to be the fastest tool available.
 
 
 In the future I'll likely try to get even better performance while still adding
 In the future I'll likely try to get even better performance while still adding
 new features, mainly for fun.<br/>
 new features, mainly for fun.<br/>

+ 0 - 1
TODO

@@ -33,4 +33,3 @@ TODO
 * nested groups: AB/ABC/ABCD/... (hints: sort, check functions)
 * nested groups: AB/ABC/ABCD/... (hints: sort, check functions)
 * multi component registry::remove and some others?
 * multi component registry::remove and some others?
 * range based registry::remove and some others?
 * range based registry::remove and some others?
-* allocation-less and faster sort

+ 10 - 12
src/entt/entity/group.hpp

@@ -378,10 +378,6 @@ public:
      * * An iterator past the last 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.
      * * 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
      * @note
      * Attempting to iterate elements using a raw pointer returned by a call to
      * 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
      * either `data` or `raw` gives no guarantees on the order, even though
@@ -400,10 +396,13 @@ public:
         if constexpr(sizeof...(Component) == 0) {
         if constexpr(sizeof...(Component) == 0) {
             static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>);
             static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>);
             handler->sort(handler->begin(), handler->end(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
             handler->sort(handler->begin(), handler->end(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
+        }  else if constexpr(sizeof...(Component) == 1) {
+            handler->sort(handler->begin(), handler->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
+                return compare((std::get<pool_type<Component> *>(pools)->get(lhs), ...), (std::get<pool_type<Component> *>(pools)->get(rhs), ...));
+            }, std::move(algo), std::forward<Args>(args)...);
         } else {
         } else {
             handler->sort(handler->begin(), handler->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
             handler->sort(handler->begin(), handler->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
-                // useless this-> used to suppress a warning with clang
-                return compare(this->get<Component...>(lhs), this->get<Component...>(rhs));
+                return compare(std::tuple<decltype(get<Component>({}))...>{std::get<pool_type<Component> *>(pools)->get(lhs)...}, std::tuple<decltype(get<Component>({}))...>{std::get<pool_type<Component> *>(pools)->get(rhs)...});
             }, std::move(algo), std::forward<Args>(args)...);
             }, std::move(algo), std::forward<Args>(args)...);
         }
         }
     }
     }
@@ -801,10 +800,6 @@ public:
      * * An iterator past the last 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.
      * * 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
      * @note
      * Attempting to iterate elements using a raw pointer returned by a call to
      * 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
      * either `data` or `raw` gives no guarantees on the order, even though
@@ -825,10 +820,13 @@ public:
         if constexpr(sizeof...(Component) == 0) {
         if constexpr(sizeof...(Component) == 0) {
             static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>);
             static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>);
             cpool->sort(cpool->end()-*length, cpool->end(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
             cpool->sort(cpool->end()-*length, cpool->end(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
+        } else if constexpr(sizeof...(Component) == 1) {
+            cpool->sort(cpool->end()-*length, cpool->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
+                return compare((std::get<pool_type<Component> *>(pools)->get(lhs), ...), (std::get<pool_type<Component> *>(pools)->get(rhs), ...));
+            }, std::move(algo), std::forward<Args>(args)...);
         } else {
         } else {
             cpool->sort(cpool->end()-*length, cpool->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
             cpool->sort(cpool->end()-*length, cpool->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
-                // useless this-> used to suppress a warning with clang
-                return compare(this->get<Component...>(lhs), this->get<Component...>(rhs));
+                return compare(std::tuple<decltype(get<Component>({}))...>{std::get<pool_type<Component> *>(pools)->get(lhs)...}, std::tuple<decltype(get<Component>({}))...>{std::get<pool_type<Component> *>(pools)->get(rhs)...});
             }, std::move(algo), std::forward<Args>(args)...);
             }, std::move(algo), std::forward<Args>(args)...);
         }
         }
 
 

+ 61 - 16
src/entt/entity/sparse_set.hpp

@@ -499,10 +499,6 @@ public:
      * * An iterator past the last 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.
      * * 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
      * @note
      * Attempting to iterate elements using a raw pointer returned by a call to
      * Attempting to iterate elements using a raw pointer returned by a call to
      * `data` gives no guarantees on the order, even though `sort` has been
      * `data` gives no guarantees on the order, even though `sort` has been
@@ -519,25 +515,74 @@ public:
      */
      */
     template<typename Compare, typename Sort = std_sort, typename... Args>
     template<typename Compare, typename Sort = std_sort, typename... Args>
     void sort(iterator_type first, iterator_type last, Compare compare, Sort algo = Sort{}, Args &&... args) {
     void sort(iterator_type first, iterator_type last, Compare compare, Sort algo = Sort{}, Args &&... args) {
-        ENTT_ASSERT(!(first > last));
+        ENTT_ASSERT(!(last < first));
+        ENTT_ASSERT(!(last > end()));
+
+        const auto length = std::distance(first, last);
+        const auto skip = std::distance(last, end());
+        const auto to = direct.rend() - skip;
+        const auto from = to - length;
+
+        algo(from, to, std::move(compare), std::forward<Args>(args)...);
+
+        for(size_type pos = skip, end = skip+length; pos < end; pos++) {
+            auto [page, offset] = map(direct[pos]);
+            reverse[page][offset] = entity_type(pos);
+        }
+    }
+
+    /**
+     * @brief Sort elements according to the given comparison function.
+     *
+     * @sa sort
+     *
+     * This function is a slightly slower version of `sort` that invokes the
+     * caller to indicate which entities are swapped.<br/>
+     * It's recommended when the caller wants to sort its own data structures to
+     * align them with the order induced in the sparse set.
+     *
+     * The signature of the callback should be equivalent to the following:
+     *
+     * @code{.cpp}
+     * bool(const Entity, const Entity);
+     * @endcode
+     *
+     * @tparam Apply Type of function object to invoke to notify the caller.
+     * @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 first An iterator to the first element of the range to sort.
+     * @param last An iterator past the last element of the range to sort.
+     * @param apply A valid function object to use as a callback.
+     * @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 Apply, typename Compare, typename Sort = std_sort, typename... Args>
+    void arrange(iterator_type first, iterator_type last, Apply apply, Compare compare, Sort algo = Sort{}, Args &&... args) {
+        ENTT_ASSERT(!(last < first));
+        ENTT_ASSERT(!(last > end()));
 
 
-        std::vector<size_type> copy(last - first);
-        const auto offset = std::distance(last, end());
-        std::iota(copy.begin(), copy.end(), size_type{});
+        const auto length = std::distance(first, last);
+        const auto skip = std::distance(last, end());
+        const auto to = direct.rend() - skip;
+        const auto from = to - length;
 
 
-        algo(copy.rbegin(), copy.rend(), [this, offset, compare = std::move(compare)](const auto lhs, const auto rhs) {
-            return compare(std::as_const(direct[lhs+offset]), std::as_const(direct[rhs+offset]));
-        }, std::forward<Args>(args)...);
+        algo(from, to, std::move(compare), std::forward<Args>(args)...);
 
 
-        for(size_type pos{}, length = copy.size(); pos < length; ++pos) {
+        for(size_type pos = skip, end = skip+length; pos < end; pos++) {
             auto curr = pos;
             auto curr = pos;
-            auto next = copy[curr];
+            auto next = index(direct[curr]);
 
 
             while(curr != next) {
             while(curr != next) {
-                swap(direct[copy[curr] + offset], direct[copy[next] + offset]);
-                copy[curr] = curr;
+                auto [src_page, src_offset] = map(direct[curr]);
+                auto [dst_page, dst_offset] = map(direct[next]);
+
+                apply(direct[curr], direct[next]);
+                std::swap(reverse[src_page][src_offset], reverse[dst_page][dst_offset]);
+
                 curr = next;
                 curr = next;
-                next = copy[curr];
+                next = index(direct[curr]);
             }
             }
         }
         }
     }
     }

+ 8 - 7
src/entt/entity/storage.hpp

@@ -426,10 +426,6 @@ public:
      * * An iterator past the last 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.
      * * 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
      * @note
      * Attempting to iterate elements using a raw pointer returned by a call to
      * 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
      * either `data` or `raw` gives no guarantees on the order, even though
@@ -446,19 +442,24 @@ public:
      */
      */
     template<typename Compare, typename Sort = std_sort, typename... Args>
     template<typename Compare, typename Sort = std_sort, typename... Args>
     void sort(iterator_type first, iterator_type last, Compare compare, Sort algo = Sort{}, Args &&... args) {
     void sort(iterator_type first, iterator_type last, Compare compare, Sort algo = Sort{}, Args &&... args) {
-        ENTT_ASSERT(!(first > last));
+        ENTT_ASSERT(!(last < first));
+        ENTT_ASSERT(!(last > end()));
 
 
         const auto from = underlying_type::begin() + std::distance(begin(), first);
         const auto from = underlying_type::begin() + std::distance(begin(), first);
         const auto to = from + std::distance(first, last);
         const auto to = from + std::distance(first, last);
 
 
+        const auto apply = [this](const auto lhs, const auto rhs) {
+            std::swap(instances[underlying_type::index(lhs)], instances[underlying_type::index(rhs)]);
+        };
+
         if constexpr(std::is_invocable_v<Compare, const object_type &, const object_type &>) {
         if constexpr(std::is_invocable_v<Compare, const object_type &, const object_type &>) {
             static_assert(!std::is_empty_v<object_type>);
             static_assert(!std::is_empty_v<object_type>);
 
 
-            underlying_type::sort(from, to, [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
+            underlying_type::arrange(from, to, std::move(apply), [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
                 return compare(std::as_const(instances[underlying_type::index(lhs)]), std::as_const(instances[underlying_type::index(rhs)]));
                 return compare(std::as_const(instances[underlying_type::index(lhs)]), std::as_const(instances[underlying_type::index(rhs)]));
             }, std::move(algo), std::forward<Args>(args)...);
             }, std::move(algo), std::forward<Args>(args)...);
         } else {
         } else {
-            underlying_type::sort(from, to, std::move(compare), std::move(algo), std::forward<Args>(args)...);
+            underlying_type::arrange(from, to, std::move(apply), std::move(compare), std::move(algo), std::forward<Args>(args)...);
         }
         }
     }
     }
 
 

+ 107 - 21
test/entt/entity/sparse_set.cpp

@@ -1,6 +1,8 @@
 #include <cstdint>
 #include <cstdint>
 #include <utility>
 #include <utility>
 #include <iterator>
 #include <iterator>
+#include <algorithm>
+#include <functional>
 #include <type_traits>
 #include <type_traits>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <entt/entity/sparse_set.hpp>
 #include <entt/entity/sparse_set.hpp>
@@ -240,9 +242,7 @@ TEST(SparseSet, SortOrdered) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{7});
     ASSERT_EQ(*(set.data() + 3u), entt::entity{7});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{3});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{3});
 
 
-    set.sort(set.begin(), set.end(), [](const auto lhs, const auto rhs) {
-        return std::underlying_type_t<entt::entity>(lhs) < std::underlying_type_t<entt::entity>(rhs);
-    });
+    set.sort(set.begin(), set.end(), std::less{});
 
 
     ASSERT_EQ(*(set.data() + 0u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 0u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
@@ -276,9 +276,7 @@ TEST(SparseSet, SortReverse) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
 
 
-    set.sort(set.begin(), set.end(), [](const auto lhs, const auto rhs) {
-        return std::underlying_type_t<entt::entity>(lhs) < std::underlying_type_t<entt::entity>(rhs);
-    });
+    set.sort(set.begin(), set.end(), std::less{});
 
 
     ASSERT_EQ(*(set.data() + 0u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 0u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
@@ -312,9 +310,7 @@ TEST(SparseSet, SortUnordered) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
 
 
-    set.sort(set.begin(), set.end(), [](const auto lhs, const auto rhs) {
-        return std::underlying_type_t<entt::entity>(lhs) < std::underlying_type_t<entt::entity>(rhs);
-    });
+    set.sort(set.begin(), set.end(), std::less{});
 
 
     ASSERT_EQ(*(set.data() + 0u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 0u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
@@ -348,9 +344,7 @@ TEST(SparseSet, SortRange) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
 
 
-    set.sort(set.end(), set.end(), [](const auto lhs, const auto rhs) {
-        return std::underlying_type_t<entt::entity>(lhs) < std::underlying_type_t<entt::entity>(rhs);
-    });
+    set.sort(set.end(), set.end(), std::less{});
 
 
     ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
     ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{7});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{7});
@@ -358,9 +352,7 @@ TEST(SparseSet, SortRange) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
 
 
-    set.sort(set.begin(), set.begin(), [](const auto lhs, const auto rhs) {
-        return std::underlying_type_t<entt::entity>(lhs) < std::underlying_type_t<entt::entity>(rhs);
-    });
+    set.sort(set.begin(), set.begin(), std::less{});
 
 
     ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
     ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{7});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{7});
@@ -368,9 +360,7 @@ TEST(SparseSet, SortRange) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
 
 
-    set.sort(set.begin()+2, set.begin()+3, [](const auto lhs, const auto rhs) {
-        return std::underlying_type_t<entt::entity>(lhs) < std::underlying_type_t<entt::entity>(rhs);
-    });
+    set.sort(set.begin()+2, set.begin()+3, std::less{});
 
 
     ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
     ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{7});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{7});
@@ -378,9 +368,7 @@ TEST(SparseSet, SortRange) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
     ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
 
 
-    set.sort(++set.begin(), --set.end(), [](const auto lhs, const auto rhs) {
-        return std::underlying_type_t<entt::entity>(lhs) < std::underlying_type_t<entt::entity>(rhs);
-    });
+    set.sort(++set.begin(), --set.end(), std::less{});
 
 
     ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
     ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
     ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
@@ -399,6 +387,104 @@ TEST(SparseSet, SortRange) {
     ASSERT_EQ(begin, end);
     ASSERT_EQ(begin, end);
 }
 }
 
 
+TEST(SparseSet, ArrangOrdered) {
+    entt::sparse_set<entt::entity> set;
+    entt::entity entities[5]{entt::entity{42}, entt::entity{12}, entt::entity{9}, entt::entity{7}, entt::entity{3}};
+    set.batch(std::begin(entities), std::end(entities));
+
+    set.arrange(set.begin(), set.end(), [](auto...) { FAIL(); }, std::less{});
+
+    ASSERT_EQ(*(set.data() + 0u), entt::entity{42});
+    ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
+    ASSERT_EQ(*(set.data() + 2u), entt::entity{9});
+    ASSERT_EQ(*(set.data() + 3u), entt::entity{7});
+    ASSERT_EQ(*(set.data() + 4u), entt::entity{3});
+
+    ASSERT_TRUE(std::equal(std::begin(entities), std::end(entities), set.data()));
+}
+
+TEST(SparseSet, ArrangeReverse) {
+    entt::sparse_set<entt::entity> set;
+    entt::entity entities[5]{entt::entity{3}, entt::entity{7}, entt::entity{9}, entt::entity{12}, entt::entity{42}};
+    set.batch(std::begin(entities), std::end(entities));
+
+    set.arrange(set.begin(), set.end(), [&set, &entities](const auto lhs, const auto rhs) {
+        std::swap(entities[set.index(lhs)], entities[set.index(rhs)]);
+    }, std::less{});
+
+    ASSERT_EQ(*(set.data() + 0u), entt::entity{42});
+    ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
+    ASSERT_EQ(*(set.data() + 2u), entt::entity{9});
+    ASSERT_EQ(*(set.data() + 3u), entt::entity{7});
+    ASSERT_EQ(*(set.data() + 4u), entt::entity{3});
+
+    ASSERT_TRUE(std::equal(std::begin(entities), std::end(entities), set.data()));
+}
+
+TEST(SparseSet, ArrangeUnordered) {
+    entt::sparse_set<entt::entity> set;
+    entt::entity entities[5]{entt::entity{9}, entt::entity{7}, entt::entity{3}, entt::entity{12}, entt::entity{42}};
+    set.batch(std::begin(entities), std::end(entities));
+
+    set.arrange(set.begin(), set.end(), [&set, &entities](const auto lhs, const auto rhs) {
+        std::swap(entities[set.index(lhs)], entities[set.index(rhs)]);
+    }, std::less{});
+
+    ASSERT_EQ(*(set.data() + 0u), entt::entity{42});
+    ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
+    ASSERT_EQ(*(set.data() + 2u), entt::entity{9});
+    ASSERT_EQ(*(set.data() + 3u), entt::entity{7});
+    ASSERT_EQ(*(set.data() + 4u), entt::entity{3});
+
+    ASSERT_TRUE(std::equal(std::begin(entities), std::end(entities), set.data()));
+}
+
+TEST(SparseSet, ArrangeRange) {
+    entt::sparse_set<entt::entity> set;
+    entt::entity entities[5]{entt::entity{9}, entt::entity{7}, entt::entity{3}, entt::entity{12}, entt::entity{42}};
+    set.batch(std::begin(entities), std::end(entities));
+
+    set.arrange(set.end(), set.end(), [&set, &entities](const auto lhs, const auto rhs) {
+        std::swap(entities[set.index(lhs)], entities[set.index(rhs)]);
+    }, std::less{});
+
+    ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
+    ASSERT_EQ(*(set.data() + 1u), entt::entity{7});
+    ASSERT_EQ(*(set.data() + 2u), entt::entity{3});
+    ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
+    ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
+
+    set.arrange(set.begin(), set.begin(), [&set, &entities](const auto lhs, const auto rhs) {
+        std::swap(entities[set.index(lhs)], entities[set.index(rhs)]);
+    }, std::less{});
+
+    ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
+    ASSERT_EQ(*(set.data() + 1u), entt::entity{7});
+    ASSERT_EQ(*(set.data() + 2u), entt::entity{3});
+    ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
+    ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
+
+    set.arrange(set.begin()+2, set.begin()+3, [&set, &entities](const auto lhs, const auto rhs) {
+        std::swap(entities[set.index(lhs)], entities[set.index(rhs)]);
+    }, std::less{});
+
+    ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
+    ASSERT_EQ(*(set.data() + 1u), entt::entity{7});
+    ASSERT_EQ(*(set.data() + 2u), entt::entity{3});
+    ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
+    ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
+
+    set.arrange(++set.begin(), --set.end(), [&set, &entities](const auto lhs, const auto rhs) {
+        std::swap(entities[set.index(lhs)], entities[set.index(rhs)]);
+    }, std::less{});
+
+    ASSERT_EQ(*(set.data() + 0u), entt::entity{9});
+    ASSERT_EQ(*(set.data() + 1u), entt::entity{12});
+    ASSERT_EQ(*(set.data() + 2u), entt::entity{7});
+    ASSERT_EQ(*(set.data() + 3u), entt::entity{3});
+    ASSERT_EQ(*(set.data() + 4u), entt::entity{42});
+}
+
 TEST(SparseSet, RespectDisjoint) {
 TEST(SparseSet, RespectDisjoint) {
     entt::sparse_set<entt::entity> lhs;
     entt::sparse_set<entt::entity> lhs;
     entt::sparse_set<entt::entity> rhs;
     entt::sparse_set<entt::entity> rhs;