scheduler.hpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. #ifndef ENTT_PROCESS_SCHEDULER_HPP
  2. #define ENTT_PROCESS_SCHEDULER_HPP
  3. #include <cstddef>
  4. #include <memory>
  5. #include <type_traits>
  6. #include <utility>
  7. #include <vector>
  8. #include "../config/config.h"
  9. #include "../core/compressed_pair.hpp"
  10. #include "fwd.hpp"
  11. #include "process.hpp"
  12. namespace entt {
  13. /**
  14. * @cond TURN_OFF_DOXYGEN
  15. * Internal details not to be documented.
  16. */
  17. namespace internal {
  18. template<typename Delta>
  19. struct basic_process_handler {
  20. virtual ~basic_process_handler() = default;
  21. virtual bool update(const Delta, void *) = 0;
  22. virtual void abort(const bool) = 0;
  23. // std::shared_ptr because of its type erased allocator which is useful here
  24. std::shared_ptr<basic_process_handler> next;
  25. };
  26. template<typename Delta, typename Type>
  27. struct process_handler final: basic_process_handler<Delta> {
  28. template<typename... Args>
  29. process_handler(Args &&...args)
  30. : process{std::forward<Args>(args)...} {}
  31. bool update(const Delta delta, void *data) override {
  32. if(process.tick(delta, data); process.rejected()) {
  33. this->next.reset();
  34. }
  35. return (process.rejected() || process.finished());
  36. }
  37. void abort(const bool immediate) override {
  38. process.abort(immediate);
  39. }
  40. Type process;
  41. };
  42. } // namespace internal
  43. /**
  44. * Internal details not to be documented.
  45. * @endcond
  46. */
  47. /**
  48. * @brief Cooperative scheduler for processes.
  49. *
  50. * A cooperative scheduler runs processes and helps managing their life cycles.
  51. *
  52. * Each process is invoked once per tick. If a process terminates, it's
  53. * removed automatically from the scheduler and it's never invoked again.<br/>
  54. * A process can also have a child. In this case, the process is replaced with
  55. * its child when it terminates if it returns with success. In case of errors,
  56. * both the process and its child are discarded.
  57. *
  58. * Example of use (pseudocode):
  59. *
  60. * @code{.cpp}
  61. * scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
  62. * // code
  63. * }).then<my_process>(arguments...);
  64. * @endcode
  65. *
  66. * In order to invoke all scheduled processes, call the `update` member function
  67. * passing it the elapsed time to forward to the tasks.
  68. *
  69. * @sa process
  70. *
  71. * @tparam Delta Type to use to provide elapsed time.
  72. * @tparam Allocator Type of allocator used to manage memory and elements.
  73. */
  74. template<typename Delta, typename Allocator>
  75. class basic_scheduler {
  76. template<typename Type>
  77. using handler_type = internal::process_handler<Delta, Type>;
  78. // std::shared_ptr because of its type erased allocator which is useful here
  79. using process_type = std::shared_ptr<internal::basic_process_handler<Delta>>;
  80. using alloc_traits = std::allocator_traits<Allocator>;
  81. using container_allocator = typename alloc_traits::template rebind_alloc<process_type>;
  82. using container_type = std::vector<process_type, container_allocator>;
  83. public:
  84. /*! @brief Allocator type. */
  85. using allocator_type = Allocator;
  86. /*! @brief Unsigned integer type. */
  87. using size_type = std::size_t;
  88. /*! @brief Unsigned integer type. */
  89. using delta_type = Delta;
  90. /*! @brief Default constructor. */
  91. basic_scheduler()
  92. : basic_scheduler{allocator_type{}} {}
  93. /**
  94. * @brief Constructs a scheduler with a given allocator.
  95. * @param allocator The allocator to use.
  96. */
  97. explicit basic_scheduler(const allocator_type &allocator)
  98. : handlers{allocator, allocator} {}
  99. /**
  100. * @brief Move constructor.
  101. * @param other The instance to move from.
  102. */
  103. basic_scheduler(basic_scheduler &&other) noexcept
  104. : handlers{std::move(other.handlers)} {}
  105. /**
  106. * @brief Allocator-extended move constructor.
  107. * @param other The instance to move from.
  108. * @param allocator The allocator to use.
  109. */
  110. basic_scheduler(basic_scheduler &&other, const allocator_type &allocator) noexcept
  111. : handlers{container_type{std::move(other.handlers.first()), allocator}, allocator} {
  112. ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying a scheduler is not allowed");
  113. }
  114. /**
  115. * @brief Move assignment operator.
  116. * @param other The instance to move from.
  117. * @return This scheduler.
  118. */
  119. basic_scheduler &operator=(basic_scheduler &&other) noexcept {
  120. ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying a scheduler is not allowed");
  121. handlers = std::move(other.handlers);
  122. return *this;
  123. }
  124. /**
  125. * @brief Exchanges the contents with those of a given scheduler.
  126. * @param other Scheduler to exchange the content with.
  127. */
  128. void swap(basic_scheduler &other) {
  129. using std::swap;
  130. swap(handlers, other.handlers);
  131. }
  132. /**
  133. * @brief Returns the associated allocator.
  134. * @return The associated allocator.
  135. */
  136. [[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
  137. return handlers.second();
  138. }
  139. /**
  140. * @brief Number of processes currently scheduled.
  141. * @return Number of processes currently scheduled.
  142. */
  143. [[nodiscard]] size_type size() const noexcept {
  144. return handlers.first().size();
  145. }
  146. /**
  147. * @brief Returns true if at least a process is currently scheduled.
  148. * @return True if there are scheduled processes, false otherwise.
  149. */
  150. [[nodiscard]] bool empty() const noexcept {
  151. return handlers.first().empty();
  152. }
  153. /**
  154. * @brief Discards all scheduled processes.
  155. *
  156. * Processes aren't aborted. They are discarded along with their children
  157. * and never executed again.
  158. */
  159. void clear() {
  160. handlers.first().clear();
  161. }
  162. /**
  163. * @brief Schedules a process for the next tick.
  164. *
  165. * Returned value can be used to attach a continuation for the last process.
  166. * The continutation is scheduled automatically when the process terminates
  167. * and only if the process returns with success.
  168. *
  169. * Example of use (pseudocode):
  170. *
  171. * @code{.cpp}
  172. * // schedules a task in the form of a process class
  173. * scheduler.attach<my_process>(arguments...)
  174. * // appends a child in the form of a lambda function
  175. * .then([](auto delta, void *, auto succeed, auto fail) {
  176. * // code
  177. * })
  178. * // appends a child in the form of another process class
  179. * .then<my_other_process>();
  180. * @endcode
  181. *
  182. * @tparam Proc Type of process to schedule.
  183. * @tparam Args Types of arguments to use to initialize the process.
  184. * @param args Parameters to use to initialize the process.
  185. * @return This process scheduler.
  186. */
  187. template<typename Proc, typename... Args>
  188. basic_scheduler &attach(Args &&...args) {
  189. static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
  190. auto &ref = handlers.first().emplace_back(std::allocate_shared<handler_type<Proc>>(handlers.second(), std::forward<Args>(args)...));
  191. // forces the process to exit the uninitialized state
  192. ref->update({}, nullptr);
  193. return *this;
  194. }
  195. /**
  196. * @brief Schedules a process for the next tick.
  197. *
  198. * A process can be either a lambda or a functor. The scheduler wraps both
  199. * of them in a process adaptor internally.<br/>
  200. * The signature of the function call operator should be equivalent to the
  201. * following:
  202. *
  203. * @code{.cpp}
  204. * void(Delta delta, void *data, auto succeed, auto fail);
  205. * @endcode
  206. *
  207. * Where:
  208. *
  209. * * `delta` is the elapsed time.
  210. * * `data` is an opaque pointer to user data if any, `nullptr` otherwise.
  211. * * `succeed` is a function to call when a process terminates with success.
  212. * * `fail` is a function to call when a process terminates with errors.
  213. *
  214. * The signature of the function call operator of both `succeed` and `fail`
  215. * is equivalent to the following:
  216. *
  217. * @code{.cpp}
  218. * void();
  219. * @endcode
  220. *
  221. * Returned value can be used to attach a continuation for the last process.
  222. * The continutation is scheduled automatically when the process terminates
  223. * and only if the process returns with success.
  224. *
  225. * Example of use (pseudocode):
  226. *
  227. * @code{.cpp}
  228. * // schedules a task in the form of a lambda function
  229. * scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
  230. * // code
  231. * })
  232. * // appends a child in the form of another lambda function
  233. * .then([](auto delta, void *, auto succeed, auto fail) {
  234. * // code
  235. * })
  236. * // appends a child in the form of a process class
  237. * .then<my_process>(arguments...);
  238. * @endcode
  239. *
  240. * @sa process_adaptor
  241. *
  242. * @tparam Func Type of process to schedule.
  243. * @param func Either a lambda or a functor to use as a process.
  244. * @return This process scheduler.
  245. */
  246. template<typename Func>
  247. basic_scheduler &attach(Func &&func) {
  248. using Proc = process_adaptor<std::decay_t<Func>, Delta>;
  249. return attach<Proc>(std::forward<Func>(func));
  250. }
  251. /**
  252. * @brief Sets a process as a continuation of the last scheduled process.
  253. * @tparam Proc Type of process to use as a continuation.
  254. * @tparam Args Types of arguments to use to initialize the process.
  255. * @param args Parameters to use to initialize the process.
  256. * @return This process scheduler.
  257. */
  258. template<typename Proc, typename... Args>
  259. basic_scheduler &then(Args &&...args) {
  260. static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
  261. ENTT_ASSERT(!handlers.first().empty(), "Process not available");
  262. auto *curr = handlers.first().back().get();
  263. for(; curr->next; curr = curr->next.get()) {}
  264. curr->next = std::allocate_shared<handler_type<Proc>>(handlers.second(), std::forward<Args>(args)...);
  265. return *this;
  266. }
  267. /**
  268. * @brief Sets a process as a continuation of the last scheduled process.
  269. * @tparam Func Type of process to use as a continuation.
  270. * @param func Either a lambda or a functor to use as a process.
  271. * @return This process scheduler.
  272. */
  273. template<typename Func>
  274. basic_scheduler &then(Func &&func) {
  275. using Proc = process_adaptor<std::decay_t<Func>, Delta>;
  276. return then<Proc>(std::forward<Func>(func));
  277. }
  278. /**
  279. * @brief Updates all scheduled processes.
  280. *
  281. * All scheduled processes are executed in no specific order.<br/>
  282. * If a process terminates with success, it's replaced with its child, if
  283. * any. Otherwise, if a process terminates with an error, it's removed along
  284. * with its child.
  285. *
  286. * @param delta Elapsed time.
  287. * @param data Optional data.
  288. */
  289. void update(const delta_type delta, void *data = nullptr) {
  290. for(auto next = handlers.first().size(); next; --next) {
  291. if(const auto pos = next - 1u; handlers.first()[pos]->update(delta, data)) {
  292. // updating might spawn/reallocate, cannot hold refs until here
  293. if(auto &curr = handlers.first()[pos]; curr->next) {
  294. curr = std::move(curr->next);
  295. // forces the process to exit the uninitialized state
  296. curr->update({}, nullptr);
  297. } else {
  298. curr = std::move(handlers.first().back());
  299. handlers.first().pop_back();
  300. }
  301. }
  302. }
  303. }
  304. /**
  305. * @brief Aborts all scheduled processes.
  306. *
  307. * Unless an immediate operation is requested, the abort is scheduled for
  308. * the next tick. Processes won't be executed anymore in any case.<br/>
  309. * Once a process is fully aborted and thus finished, it's discarded along
  310. * with its child, if any.
  311. *
  312. * @param immediate Requests an immediate operation.
  313. */
  314. void abort(const bool immediate = false) {
  315. for(auto &&curr: handlers.first()) {
  316. curr->abort(immediate);
  317. }
  318. }
  319. private:
  320. compressed_pair<container_type, allocator_type> handlers;
  321. };
  322. } // namespace entt
  323. #endif