Procházet zdrojové kódy

sorting no longer requires allocations and is much faster

Michele Caini před 6 roky
rodič
revize
800751cbe0

+ 28 - 39
README.md

@@ -183,47 +183,36 @@ amazing set of features. And even more, of course.
 ## Performance
 
 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
-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
 new features, mainly for fun.<br/>

+ 0 - 1
TODO

@@ -33,4 +33,3 @@ TODO
 * nested groups: AB/ABC/ABCD/... (hints: sort, check functions)
 * multi component 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.
      * * 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
@@ -400,10 +396,13 @@ public:
         if constexpr(sizeof...(Component) == 0) {
             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)...);
+        }  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 {
             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)...);
         }
     }
@@ -801,10 +800,6 @@ public:
      * * 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
@@ -825,10 +820,13 @@ public:
         if constexpr(sizeof...(Component) == 0) {
             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)...);
+        } 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 {
             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)...);
         }
 

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

@@ -499,10 +499,6 @@ public:
      * * 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
      * `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>
     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 next = copy[curr];
+            auto next = index(direct[curr]);
 
             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;
-                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.
      * * 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
@@ -446,19 +442,24 @@ public:
      */
     template<typename Compare, typename Sort = std_sort, typename... 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 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 &>) {
             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)]));
             }, std::move(algo), std::forward<Args>(args)...);
         } 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 <utility>
 #include <iterator>
+#include <algorithm>
+#include <functional>
 #include <type_traits>
 #include <gtest/gtest.h>
 #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() + 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() + 1u), entt::entity{12});
@@ -276,9 +276,7 @@ TEST(SparseSet, SortReverse) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     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() + 1u), entt::entity{12});
@@ -312,9 +310,7 @@ TEST(SparseSet, SortUnordered) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     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() + 1u), entt::entity{12});
@@ -348,9 +344,7 @@ TEST(SparseSet, SortRange) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     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() + 1u), entt::entity{7});
@@ -358,9 +352,7 @@ TEST(SparseSet, SortRange) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     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() + 1u), entt::entity{7});
@@ -368,9 +360,7 @@ TEST(SparseSet, SortRange) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     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() + 1u), entt::entity{7});
@@ -378,9 +368,7 @@ TEST(SparseSet, SortRange) {
     ASSERT_EQ(*(set.data() + 3u), entt::entity{12});
     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() + 1u), entt::entity{12});
@@ -399,6 +387,104 @@ TEST(SparseSet, SortRange) {
     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) {
     entt::sparse_set<entt::entity> lhs;
     entt::sparse_set<entt::entity> rhs;