process.hpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. #ifndef ENTT_PROCESS_PROCESS_HPP
  2. #define ENTT_PROCESS_PROCESS_HPP
  3. #include <cstdint>
  4. #include <type_traits>
  5. #include <utility>
  6. #include "fwd.hpp"
  7. namespace entt {
  8. /**
  9. * @brief Base class for processes.
  10. *
  11. * This class stays true to the CRTP idiom. Derived classes must specify what's
  12. * the intended type for elapsed times.<br/>
  13. * A process should expose publicly the following member functions whether
  14. * required:
  15. *
  16. * * @code{.cpp}
  17. * void update(Delta, void *);
  18. * @endcode
  19. *
  20. * It's invoked once per tick until a process is explicitly aborted or it
  21. * terminates either with or without errors. Even though it's not mandatory to
  22. * declare this member function, as a rule of thumb each process should at
  23. * least define it to work properly. The `void *` parameter is an opaque
  24. * pointer to user data (if any) forwarded directly to the process during an
  25. * update.
  26. *
  27. * * @code{.cpp}
  28. * void init();
  29. * @endcode
  30. *
  31. * It's invoked when the process joins the running queue of a scheduler. This
  32. * happens as soon as it's attached to the scheduler if the process is a top
  33. * level one, otherwise when it replaces its parent if the process is a
  34. * continuation.
  35. *
  36. * * @code{.cpp}
  37. * void succeeded();
  38. * @endcode
  39. *
  40. * It's invoked in case of success, immediately after an update and during the
  41. * same tick.
  42. *
  43. * * @code{.cpp}
  44. * void failed();
  45. * @endcode
  46. *
  47. * It's invoked in case of errors, immediately after an update and during the
  48. * same tick.
  49. *
  50. * * @code{.cpp}
  51. * void aborted();
  52. * @endcode
  53. *
  54. * It's invoked only if a process is explicitly aborted. There is no guarantee
  55. * that it executes in the same tick, this depends solely on whether the
  56. * process is aborted immediately or not.
  57. *
  58. * Derived classes can change the internal state of a process by invoking the
  59. * `succeed` and `fail` protected member functions and even pause or unpause the
  60. * process itself.
  61. *
  62. * @sa scheduler
  63. *
  64. * @tparam Derived Actual type of process that extends the class template.
  65. * @tparam Delta Type to use to provide elapsed time.
  66. */
  67. template<typename Derived, typename Delta>
  68. class process {
  69. enum class state : std::uint8_t {
  70. uninitialized = 0,
  71. running,
  72. paused,
  73. succeeded,
  74. failed,
  75. aborted,
  76. finished,
  77. rejected
  78. };
  79. template<typename Target = Derived>
  80. auto next(std::integral_constant<state, state::uninitialized>)
  81. -> decltype(std::declval<Target>().init(), void()) {
  82. static_cast<Target *>(this)->init();
  83. }
  84. template<typename Target = Derived>
  85. auto next(std::integral_constant<state, state::running>, Delta delta, void *data)
  86. -> decltype(std::declval<Target>().update(delta, data), void()) {
  87. static_cast<Target *>(this)->update(delta, data);
  88. }
  89. template<typename Target = Derived>
  90. auto next(std::integral_constant<state, state::succeeded>)
  91. -> decltype(std::declval<Target>().succeeded(), void()) {
  92. static_cast<Target *>(this)->succeeded();
  93. }
  94. template<typename Target = Derived>
  95. auto next(std::integral_constant<state, state::failed>)
  96. -> decltype(std::declval<Target>().failed(), void()) {
  97. static_cast<Target *>(this)->failed();
  98. }
  99. template<typename Target = Derived>
  100. auto next(std::integral_constant<state, state::aborted>)
  101. -> decltype(std::declval<Target>().aborted(), void()) {
  102. static_cast<Target *>(this)->aborted();
  103. }
  104. void next(...) const noexcept {}
  105. protected:
  106. /**
  107. * @brief Terminates a process with success if it's still alive.
  108. *
  109. * The function is idempotent and it does nothing if the process isn't
  110. * alive.
  111. */
  112. void succeed() noexcept {
  113. if(alive()) {
  114. current = state::succeeded;
  115. }
  116. }
  117. /**
  118. * @brief Terminates a process with errors if it's still alive.
  119. *
  120. * The function is idempotent and it does nothing if the process isn't
  121. * alive.
  122. */
  123. void fail() noexcept {
  124. if(alive()) {
  125. current = state::failed;
  126. }
  127. }
  128. /**
  129. * @brief Stops a process if it's in a running state.
  130. *
  131. * The function is idempotent and it does nothing if the process isn't
  132. * running.
  133. */
  134. void pause() noexcept {
  135. if(current == state::running) {
  136. current = state::paused;
  137. }
  138. }
  139. /**
  140. * @brief Restarts a process if it's paused.
  141. *
  142. * The function is idempotent and it does nothing if the process isn't
  143. * paused.
  144. */
  145. void unpause() noexcept {
  146. if(current == state::paused) {
  147. current = state::running;
  148. }
  149. }
  150. public:
  151. /*! @brief Type used to provide elapsed time. */
  152. using delta_type = Delta;
  153. /*! @brief Default destructor. */
  154. virtual ~process() noexcept {
  155. static_assert(std::is_base_of_v<process, Derived>, "Incorrect use of the class template");
  156. }
  157. /**
  158. * @brief Aborts a process if it's still alive.
  159. *
  160. * The function is idempotent and it does nothing if the process isn't
  161. * alive.
  162. *
  163. * @param immediate Requests an immediate operation.
  164. */
  165. void abort(const bool immediate = false) {
  166. if(alive()) {
  167. current = state::aborted;
  168. if(immediate) {
  169. tick({});
  170. }
  171. }
  172. }
  173. /**
  174. * @brief Returns true if a process is either running or paused.
  175. * @return True if the process is still alive, false otherwise.
  176. */
  177. [[nodiscard]] bool alive() const noexcept {
  178. return current == state::running || current == state::paused;
  179. }
  180. /**
  181. * @brief Returns true if a process is already terminated.
  182. * @return True if the process is terminated, false otherwise.
  183. */
  184. [[nodiscard]] bool finished() const noexcept {
  185. return current == state::finished;
  186. }
  187. /**
  188. * @brief Returns true if a process is currently paused.
  189. * @return True if the process is paused, false otherwise.
  190. */
  191. [[nodiscard]] bool paused() const noexcept {
  192. return current == state::paused;
  193. }
  194. /**
  195. * @brief Returns true if a process terminated with errors.
  196. * @return True if the process terminated with errors, false otherwise.
  197. */
  198. [[nodiscard]] bool rejected() const noexcept {
  199. return current == state::rejected;
  200. }
  201. /**
  202. * @brief Updates a process and its internal state if required.
  203. * @param delta Elapsed time.
  204. * @param data Optional data.
  205. */
  206. void tick(const Delta delta, void *data = nullptr) {
  207. switch(current) {
  208. case state::uninitialized:
  209. next(std::integral_constant<state, state::uninitialized>{});
  210. current = state::running;
  211. break;
  212. case state::running:
  213. next(std::integral_constant<state, state::running>{}, delta, data);
  214. break;
  215. default:
  216. // suppress warnings
  217. break;
  218. }
  219. // if it's dead, it must be notified and removed immediately
  220. switch(current) {
  221. case state::succeeded:
  222. next(std::integral_constant<state, state::succeeded>{});
  223. current = state::finished;
  224. break;
  225. case state::failed:
  226. next(std::integral_constant<state, state::failed>{});
  227. current = state::rejected;
  228. break;
  229. case state::aborted:
  230. next(std::integral_constant<state, state::aborted>{});
  231. current = state::rejected;
  232. break;
  233. default:
  234. // suppress warnings
  235. break;
  236. }
  237. }
  238. private:
  239. state current{state::uninitialized};
  240. };
  241. /**
  242. * @brief Adaptor for lambdas and functors to turn them into processes.
  243. *
  244. * Lambdas and functors can't be used directly with a scheduler for they are not
  245. * properly defined processes with managed life cycles.<br/>
  246. * This class helps in filling the gap and turning lambdas and functors into
  247. * full featured processes usable by a scheduler.
  248. *
  249. * The signature of the function call operator should be equivalent to the
  250. * following:
  251. *
  252. * @code{.cpp}
  253. * void(Delta delta, void *data, auto succeed, auto fail);
  254. * @endcode
  255. *
  256. * Where:
  257. *
  258. * * `delta` is the elapsed time.
  259. * * `data` is an opaque pointer to user data if any, `nullptr` otherwise.
  260. * * `succeed` is a function to call when a process terminates with success.
  261. * * `fail` is a function to call when a process terminates with errors.
  262. *
  263. * The signature of the function call operator of both `succeed` and `fail`
  264. * is equivalent to the following:
  265. *
  266. * @code{.cpp}
  267. * void();
  268. * @endcode
  269. *
  270. * Usually users shouldn't worry about creating adaptors. A scheduler will
  271. * create them internally each and avery time a lambda or a functor is used as
  272. * a process.
  273. *
  274. * @sa process
  275. * @sa scheduler
  276. *
  277. * @tparam Func Actual type of process.
  278. * @tparam Delta Type to use to provide elapsed time.
  279. */
  280. template<typename Func, typename Delta>
  281. struct process_adaptor: process<process_adaptor<Func, Delta>, Delta>, private Func {
  282. /**
  283. * @brief Constructs a process adaptor from a lambda or a functor.
  284. * @tparam Args Types of arguments to use to initialize the actual process.
  285. * @param args Parameters to use to initialize the actual process.
  286. */
  287. template<typename... Args>
  288. process_adaptor(Args &&...args)
  289. : Func{std::forward<Args>(args)...} {}
  290. /**
  291. * @brief Updates a process and its internal state if required.
  292. * @param delta Elapsed time.
  293. * @param data Optional data.
  294. */
  295. void update(const Delta delta, void *data) {
  296. Func::operator()(
  297. delta,
  298. data,
  299. [this]() { this->succeed(); },
  300. [this]() { this->fail(); });
  301. }
  302. };
  303. } // namespace entt
  304. #endif