Browse Source

custom sort function (#72)

Michele Caini 7 years ago
parent
commit
d3b6ed78d9

+ 7 - 0
README.md

@@ -751,6 +751,13 @@ In fact, there are two functions that respond to slightly different needs:
   });
   ```
 
+  There exists also the possibility to use a custom sort function object, as
+  long as it adheres to the requirements described in the
+  [official documentation](https://skypjack.github.io/entt/).<br/>
+  This is possible mainly because users can get much more with a custom sort
+  function object if the pattern of usage is known. As an example, in case of an
+  almost sorted pool, quick sort could be much, much slower than insertion sort.
+
 * Components can be sorted according to the order imposed by another component:
 
   ```cpp

+ 3 - 1
TODO

@@ -5,8 +5,10 @@
 * define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
 * define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
 * does it worth it to add an optional functor to the member functions of snapshot so as to filter out instances and entities?
-* allow for custom sort functions on registry/view sort (unlikely std::sort is the best function ever in game programming)
 * ease the assignment of tags as string (use a template class with a non-type template parameter behind the scene)
+* add a small comparison of benchmarks for the single component view somewhere, to give a grasp of the performance
+* add benchmarks for sorting in case of almost sorted array and insertion sort
 * prototype entities, a really interesting feature (see #56)
+* is registry/utility.hpp really required?
 * "singleton mode" for tags (see #66)
 * AOB

+ 76 - 0
src/entt/core/algorithm.hpp

@@ -0,0 +1,76 @@
+#ifndef ENTT_CORE_ALGORITHM_HPP
+#define ENTT_CORE_ALGORITHM_HPP
+
+
+#include <functional>
+#include <algorithm>
+#include <utility>
+
+
+namespace entt {
+
+
+/**
+ * @brief Function object to wrap `std::sort` in a class type.
+ *
+ * Unfortunately, `std::sort` cannot be passed as template argument to a class
+ * template or a function template.<br/>
+ * This class fills the gap by wrapping some flavors of `std::sort` in a
+ * function object.
+ */
+struct StdSort {
+    /**
+     * @brief Sorts the element in a range.
+     *
+     * Sorts the element in a range using the given binary comparison function.
+     *
+     * @tparam It Type of random access iterator.
+     * @tparam Compare Type of comparison 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 compare A valid comparison function object.
+     */
+    template<typename It, typename Compare = std::less<>>
+    void operator()(It first, It last, Compare compare = Compare{}) {
+        std::sort(std::move(first), std::move(last), std::move(compare));
+    }
+};
+
+
+/*! @brief Function object for performing insertion sort. */
+struct InsertionSort {
+    /**
+     * @brief Sorts the element in a range.
+     *
+     * Sorts the element in a range using the given binary comparison function.
+     *
+     * @tparam It Type of random access iterator.
+     * @tparam Compare Type of comparison 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 compare A valid comparison function object.
+     */
+    template<typename It, typename Compare = std::less<>>
+    void operator()(It first, It last, Compare compare = Compare{}) {
+        auto it = first + 1;
+
+        while(it != last) {
+            auto value = *it;
+            auto pre = it;
+
+            while(pre != first && compare(value, *(pre-1))) {
+                *pre = *(pre-1);
+                --pre;
+            }
+
+            *pre = value;
+            ++it;
+        }
+    }
+};
+
+
+}
+
+
+#endif // ENTT_CORE_ALGORITHM_HPP

+ 20 - 3
src/entt/entity/registry.hpp

@@ -12,6 +12,7 @@
 #include <algorithm>
 #include <type_traits>
 #include "../config/config.h"
+#include "../core/algorithm.hpp"
 #include "../core/family.hpp"
 #include "../signal/sigh.hpp"
 #include "entt_traits.hpp"
@@ -967,14 +968,30 @@ public:
      * bool(const Component &, const Component &)
      * @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 funtion 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.
+     *
      * @tparam Component Type of components to sort.
      * @tparam Compare Type of comparison function object.
+     * @tparam Sort Type of sort function object.
      * @param compare A valid comparison function object.
+     * @param sort A valid sort function object.
      */
-    template<typename Component, typename Compare>
-    void sort(Compare compare) {
+    template<typename Component, typename Compare, typename Sort = StdSort>
+    void sort(Compare compare, Sort sort = Sort{}) {
         assure<Component>();
-        pool<Component>().sort(std::move(compare));
+        pool<Component>().sort(std::move(compare), std::move(sort));
     }
 
     /**

+ 20 - 3
src/entt/entity/sparse_set.hpp

@@ -11,6 +11,7 @@
 #include <cassert>
 #include <type_traits>
 #include "../config/config.h"
+#include "../core/algorithm.hpp"
 #include "entt_traits.hpp"
 
 
@@ -787,20 +788,36 @@ public:
      * bool(const Type &, const Type &)
      * @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 funtion 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.
      * @param compare A valid comparison function object.
+     * @param sort A valid sort function object.
      */
-    template<typename Compare>
-    void sort(Compare compare) {
+    template<typename Compare, typename Sort = StdSort>
+    void sort(Compare compare, Sort sort = Sort{}) {
         std::vector<pos_type> 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) {
+        sort(copy.begin(), copy.end(), [this, compare = std::move(compare)](auto lhs, auto rhs) {
             return compare(const_cast<const object_type &>(instances[rhs]), const_cast<const object_type &>(instances[lhs]));
         });
 

+ 1 - 0
src/entt/entt.hpp

@@ -1,3 +1,4 @@
+#include "core/algorithm.hpp"
 #include "core/family.hpp"
 #include "core/hashed_string.hpp"
 #include "core/ident.hpp"

+ 1 - 0
test/CMakeLists.txt

@@ -45,6 +45,7 @@ endif()
 
 # Test core
 
+ADD_ENTT_TEST(algorithm entt/core/algorithm.cpp)
 ADD_ENTT_TEST(family entt/core/family.cpp)
 ADD_ENTT_TEST(hashed_string entt/core/hashed_string.cpp)
 ADD_ENTT_TEST(ident entt/core/ident.cpp)

+ 60 - 0
test/benchmark/benchmark.cpp

@@ -568,3 +568,63 @@ TEST(Benchmark, SortMulti) {
 
     timer.elapsed();
 }
+
+TEST(Benchmark, AlmostSortedStdSort) {
+    entt::DefaultRegistry registry;
+    entt::DefaultRegistry::entity_type entities[3];
+
+    std::cout << "Sort 150000 entities, almost sorted, std::sort" << std::endl;
+
+    for(std::uint64_t i = 0; i < 150000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Position>(entity, i, i);
+
+        if(!(i % 50000)) {
+            entities[i / 50000] = entity;
+        }
+    }
+
+    for(std::uint64_t i = 0; i < 3; ++i) {
+        registry.destroy(entities[i]);
+        const auto entity = registry.create();
+        registry.assign<Position>(entity, 50000 * i, 50000 * i);
+    }
+
+    Timer timer;
+
+    registry.sort<Position>([](const auto &lhs, const auto &rhs) {
+        return lhs.x > rhs.x && lhs.y > rhs.y;
+    });
+
+    timer.elapsed();
+}
+
+TEST(Benchmark, AlmostSortedInsertionSort) {
+    entt::DefaultRegistry registry;
+    entt::DefaultRegistry::entity_type entities[3];
+
+    std::cout << "Sort 150000 entities, almost sorted, insertion sort" << std::endl;
+
+    for(std::uint64_t i = 0; i < 150000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Position>(entity, i, i);
+
+        if(!(i % 50000)) {
+            entities[i / 50000] = entity;
+        }
+    }
+
+    for(std::uint64_t i = 0; i < 3; ++i) {
+        registry.destroy(entities[i]);
+        const auto entity = registry.create();
+        registry.assign<Position>(entity, 50000 * i, 50000 * i);
+    }
+
+    Timer timer;
+
+    registry.sort<Position>([](const auto &lhs, const auto &rhs) {
+        return lhs.x > rhs.x && lhs.y > rhs.y;
+    }, entt::InsertionSort{});
+
+    timer.elapsed();
+}

+ 26 - 0
test/entt/core/algorithm.cpp

@@ -0,0 +1,26 @@
+#include <array>
+#include <gtest/gtest.h>
+#include <entt/core/algorithm.hpp>
+
+TEST(Algorithm, StdSort) {
+    // well, I'm pretty sure it works, it's std::sort!!
+    std::array<int, 5> arr = { 4, 1, 3, 2, 0 };
+    entt::StdSort sort;
+
+    sort(arr.begin(), arr.end());
+
+    for(auto i = 0; i < 4; ++i) {
+        ASSERT_LT(arr[i], arr[i+1]);
+    }
+}
+
+TEST(Algorithm, InsertionSort) {
+    std::array<int, 5> arr = { 4, 1, 3, 2, 0 };
+    entt::InsertionSort sort;
+
+    sort(arr.begin(), arr.end());
+
+    for(auto i = 0; i < 4; ++i) {
+        ASSERT_LT(arr[i], arr[i+1]);
+    }
+}