Browse Source

scheduler: allocator-aware implementation

Michele Caini 2 years ago
parent
commit
16d43d2975
2 changed files with 81 additions and 25 deletions
  1. 2 1
      src/entt/process/fwd.hpp
  2. 79 24
      src/entt/process/scheduler.hpp

+ 2 - 1
src/entt/process/fwd.hpp

@@ -2,13 +2,14 @@
 #define ENTT_PROCESS_FWD_HPP
 #define ENTT_PROCESS_FWD_HPP
 
 
 #include <cstdint>
 #include <cstdint>
+#include <memory>
 
 
 namespace entt {
 namespace entt {
 
 
 template<typename, typename>
 template<typename, typename>
 class process;
 class process;
 
 
-template<typename = std::uint32_t>
+template<typename = std::uint32_t, typename = std::allocator<void>>
 class basic_scheduler;
 class basic_scheduler;
 
 
 /*! @brief Alias declaration for the most common use case. */
 /*! @brief Alias declaration for the most common use case. */

+ 79 - 24
src/entt/process/scheduler.hpp

@@ -7,6 +7,7 @@
 #include <utility>
 #include <utility>
 #include <vector>
 #include <vector>
 #include "../config/config.h"
 #include "../config/config.h"
+#include "../core/compressed_pair.hpp"
 #include "fwd.hpp"
 #include "fwd.hpp"
 #include "process.hpp"
 #include "process.hpp"
 
 
@@ -26,7 +27,8 @@ struct basic_process_handler {
     virtual bool update(const Delta, void *) = 0;
     virtual bool update(const Delta, void *) = 0;
     virtual void abort(const bool) = 0;
     virtual void abort(const bool) = 0;
 
 
-    std::unique_ptr<basic_process_handler> next;
+    // std::shared_ptr because of its type erased allocator which is useful here
+    std::shared_ptr<basic_process_handler> next;
 };
 };
 
 
 template<typename Delta, typename Type>
 template<typename Delta, typename Type>
@@ -82,37 +84,90 @@ struct process_handler final: basic_process_handler<Delta> {
  * @sa process
  * @sa process
  *
  *
  * @tparam Delta Type to use to provide elapsed time.
  * @tparam Delta Type to use to provide elapsed time.
+ * @tparam Allocator Type of allocator used to manage memory and elements.
  */
  */
-template<typename Delta>
+template<typename Delta, typename Allocator>
 class basic_scheduler {
 class basic_scheduler {
     template<typename Type>
     template<typename Type>
     using handler_type = internal::process_handler<Delta, Type>;
     using handler_type = internal::process_handler<Delta, Type>;
 
 
-    using process_type = std::unique_ptr<internal::basic_process_handler<Delta>>;
+    // std::shared_ptr because of its type erased allocator which is useful here
+    using process_type = std::shared_ptr<internal::basic_process_handler<Delta>>;
+
+    using alloc_traits = std::allocator_traits<Allocator>;
+    using container_allocator = typename alloc_traits::template rebind_alloc<process_type>;
+    using container_type = std::vector<process_type, container_allocator>;
 
 
 public:
 public:
-    /*! @brief Unsigned integer type. */
-    using delta_type = Delta;
+    /*! @brief Allocator type. */
+    using allocator_type = Allocator;
     /*! @brief Unsigned integer type. */
     /*! @brief Unsigned integer type. */
     using size_type = std::size_t;
     using size_type = std::size_t;
+    /*! @brief Unsigned integer type. */
+    using delta_type = Delta;
 
 
     /*! @brief Default constructor. */
     /*! @brief Default constructor. */
     basic_scheduler()
     basic_scheduler()
-        : handlers{} {
+        : basic_scheduler{allocator_type{}} {}
+
+    /**
+     * @brief Constructs a scheduler with a given allocator.
+     * @param allocator The allocator to use.
+     */
+    explicit basic_scheduler(const allocator_type &allocator)
+        : handlers{allocator, allocator} {}
+
+    /**
+     * @brief Move constructor.
+     * @param other The instance to move from.
+     */
+    basic_scheduler(basic_scheduler &&other) noexcept
+        : handlers{std::move(other.handlers)} {}
+
+    /**
+     * @brief Allocator-extended move constructor.
+     * @param other The instance to move from.
+     * @param allocator The allocator to use.
+     */
+    basic_scheduler(basic_scheduler &&other, const allocator_type &allocator) noexcept
+        : handlers{container_type{std::move(other.handlers.first()), allocator}, allocator} {
+        ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying a scheduler is not allowed");
     }
     }
 
 
-    /*! @brief Default move constructor. */
-    basic_scheduler(basic_scheduler &&) = default;
+    /**
+     * @brief Move assignment operator.
+     * @param other The instance to move from.
+     * @return This scheduler.
+     */
+    basic_scheduler &operator=(basic_scheduler &&other) noexcept {
+        ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying a scheduler is not allowed");
+        handlers = std::move(other.handlers);
+        return *this;
+    }
 
 
-    /*! @brief Default move assignment operator. @return This scheduler. */
-    basic_scheduler &operator=(basic_scheduler &&) = default;
+    /**
+     * @brief Exchanges the contents with those of a given scheduler.
+     * @param other Scheduler to exchange the content with.
+     */
+    void swap(basic_scheduler &other) {
+        using std::swap;
+        swap(handlers, other.handlers);
+    }
+
+    /**
+     * @brief Returns the associated allocator.
+     * @return The associated allocator.
+     */
+    [[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
+        return handlers.second();
+    }
 
 
     /**
     /**
      * @brief Number of processes currently scheduled.
      * @brief Number of processes currently scheduled.
      * @return Number of processes currently scheduled.
      * @return Number of processes currently scheduled.
      */
      */
     [[nodiscard]] size_type size() const noexcept {
     [[nodiscard]] size_type size() const noexcept {
-        return handlers.size();
+        return handlers.first().size();
     }
     }
 
 
     /**
     /**
@@ -120,7 +175,7 @@ public:
      * @return True if there are scheduled processes, false otherwise.
      * @return True if there are scheduled processes, false otherwise.
      */
      */
     [[nodiscard]] bool empty() const noexcept {
     [[nodiscard]] bool empty() const noexcept {
-        return handlers.empty();
+        return handlers.first().empty();
     }
     }
 
 
     /**
     /**
@@ -130,7 +185,7 @@ public:
      * and never executed again.
      * and never executed again.
      */
      */
     void clear() {
     void clear() {
-        handlers.clear();
+        handlers.first().clear();
     }
     }
 
 
     /**
     /**
@@ -161,7 +216,7 @@ public:
     template<typename Proc, typename... Args>
     template<typename Proc, typename... Args>
     basic_scheduler &attach(Args &&...args) {
     basic_scheduler &attach(Args &&...args) {
         static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
         static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
-        auto &ref = handlers.emplace_back(std::make_unique<handler_type<Proc>>(std::forward<Args>(args)...));
+        auto &ref = handlers.first().emplace_back(std::allocate_shared<handler_type<Proc>>(handlers.second(), std::forward<Args>(args)...));
         // forces the process to exit the uninitialized state
         // forces the process to exit the uninitialized state
         ref->update({}, nullptr);
         ref->update({}, nullptr);
         return *this;
         return *this;
@@ -234,10 +289,10 @@ public:
     template<typename Proc, typename... Args>
     template<typename Proc, typename... Args>
     basic_scheduler &then(Args &&...args) {
     basic_scheduler &then(Args &&...args) {
         static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
         static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
-        ENTT_ASSERT(!handlers.empty(), "Process not available");
-        auto *curr = handlers.back().get();
+        ENTT_ASSERT(!handlers.first().empty(), "Process not available");
+        auto *curr = handlers.first().back().get();
         for(; curr->next; curr = curr->next.get()) {}
         for(; curr->next; curr = curr->next.get()) {}
-        curr->next = std::make_unique<handler_type<Proc>>(std::forward<Args>(args)...);
+        curr->next = std::allocate_shared<handler_type<Proc>>(handlers.second(), std::forward<Args>(args)...);
         return *this;
         return *this;
     }
     }
 
 
@@ -265,16 +320,16 @@ public:
      * @param data Optional data.
      * @param data Optional data.
      */
      */
     void update(const delta_type delta, void *data = nullptr) {
     void update(const delta_type delta, void *data = nullptr) {
-        for(auto next = handlers.size(); next; --next) {
-            if(const auto pos = next - 1u; handlers[pos]->update(delta, data)) {
+        for(auto next = handlers.first().size(); next; --next) {
+            if(const auto pos = next - 1u; handlers.first()[pos]->update(delta, data)) {
                 // updating might spawn/reallocate, cannot hold refs until here
                 // updating might spawn/reallocate, cannot hold refs until here
-                if(auto &curr = handlers[pos]; curr->next) {
+                if(auto &curr = handlers.first()[pos]; curr->next) {
                     curr = std::move(curr->next);
                     curr = std::move(curr->next);
                     // forces the process to exit the uninitialized state
                     // forces the process to exit the uninitialized state
                     curr->update({}, nullptr);
                     curr->update({}, nullptr);
                 } else {
                 } else {
-                    curr = std::move(handlers.back());
-                    handlers.pop_back();
+                    curr = std::move(handlers.first().back());
+                    handlers.first().pop_back();
                 }
                 }
             }
             }
         }
         }
@@ -291,13 +346,13 @@ public:
      * @param immediate Requests an immediate operation.
      * @param immediate Requests an immediate operation.
      */
      */
     void abort(const bool immediate = false) {
     void abort(const bool immediate = false) {
-        for(auto &&curr: handlers) {
+        for(auto &&curr: handlers.first()) {
             curr->abort(immediate);
             curr->abort(immediate);
         }
         }
     }
     }
 
 
 private:
 private:
-    std::vector<process_type> handlers{};
+    compressed_pair<container_type, allocator_type> handlers;
 };
 };
 
 
 } // namespace entt
 } // namespace entt