text: #ifndef ENTT_PROCESS_SCHEDULER_HPP
  #define ENTT_PROCESS_SCHEDULER_HPP
  
  #include <algorithm>
  #include <iterator>
  #include <memory>
  #include <type_traits>
  #include <utility>
  #include <vector>
  #include "../config/config.h"
  #include "process.hpp"
  
  namespace entt {
  
  /**
   * @brief Cooperative scheduler for processes.
   *
   * A cooperative scheduler runs processes and helps managing their life cycles.
   *
   * Each process is invoked once per tick. If a process terminates, it's
   * removed automatically from the scheduler and it's never invoked again.<br/>
   * A process can also have a child. In this case, the process is replaced with
   * its child when it terminates if it returns with success. In case of errors,
   * both the process and its child are discarded.
   *
   * Example of use (pseudocode):
   *
   * @code{.cpp}
   * scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
   *     // code
   * }).then<my_process>(arguments...);
   * @endcode
   *
   * In order to invoke all scheduled processes, call the `update` member function
   * passing it the elapsed time to forward to the tasks.
   *
   * @sa process
   *
   * @tparam Delta Type to use to provide elapsed time.
   */
  template<typename Delta>
  class scheduler {
      struct process_handler {
          using instance_type = std::unique_ptr<void, void (*)(void *)>;
          using update_fn_type = bool(scheduler &, std::size_t, Delta, void *);
          using abort_fn_type = void(scheduler &, std::size_t, bool);
          using next_type = std::unique_ptr<process_handler>;
  
          instance_type instance;
          update_fn_type *update;
          abort_fn_type *abort;
          next_type next;
      };
  
      struct continuation {
          continuation(process_handler *ref) ENTT_NOEXCEPT
              : handler{ref} {}
  
          template<typename Proc, typename... Args>
          continuation then(Args &&...args) {
              static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
              auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
              handler->next.reset(new process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr});
              handler = handler->next.get();
              return *this;
          }
  
          template<typename Func>
          continuation then(Func &&func) {
              return then<process_adaptor<std::decay_t<Func>, Delta>>(std::forward<Func>(func));
          }
  
      private:
          process_handler *handler;
      };
  
      template<typename Proc>
      [[nodiscard]] static bool update(scheduler &owner, std::size_t pos, const Delta delta, void *data) {
          auto *process = static_cast<Proc *>(owner.handlers[pos].instance.get());
          process->tick(delta, data);
  
          if(process->rejected()) {
              return true;
          } else if(process->finished()) {
              if(auto &&handler = owner.handlers[pos]; handler.next) {
                  handler = std::move(*handler.next);
                  // forces the process to exit the uninitialized state
                  return handler.update(owner, pos, {}, nullptr);
              }
  
              return true;
          }
  
          return false;
      }
  
      template<typename Proc>
      static void abort(scheduler &owner, std::size_t pos, const bool immediately) {
          static_cast<Proc *>(owner.handlers[pos].instance.get())->abort(immediately);
      }
  
      template<typename Proc>
      static void deleter(void *proc) {
          delete static_cast<Proc *>(proc);
      }
  
  public:
      /*! @brief Unsigned integer type. */
      using size_type = std::size_t;
  
      /*! @brief Default constructor. */
      scheduler() = default;
  
      /*! @brief Default move constructor. */
      scheduler(scheduler &&) = default;
  
      /*! @brief Default move assignment operator. @return This scheduler. */
      scheduler &operator=(scheduler &&) = default;
  
      /**
       * @brief Number of processes currently scheduled.
       * @return Number of processes currently scheduled.
       */
      [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
          return handlers.size();
      }
  
      /**
       * @brief Returns true if at least a process is currently scheduled.
       * @return True if there are scheduled processes, false otherwise.
       */
      [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
          return handlers.empty();
      }
  
      /**
       * @brief Discards all scheduled processes.
       *
       * Processes aren't aborted. They are discarded along with their children
       * and never executed again.
       */
      void clear() {
          handlers.clear();
      }
  
      /**
       * @brief Schedules a process for the next tick.
       *
       * Returned value is an opaque object that can be used to attach a child to
       * the given process. The child is automatically scheduled when the process
       * terminates and only if the process returns with success.
       *
       * Example of use (pseudocode):
       *
       * @code{.cpp}
       * // schedules a task in the form of a process class
       * scheduler.attach<my_process>(arguments...)
       * // appends a child in the form of a lambda function
       * .then([](auto delta, void *, auto succeed, auto fail) {
       *     // code
       * })
       * // appends a child in the form of another process class
       * .then<my_other_process>();
       * @endcode
       *
       * @tparam Proc Type of process to schedule.
       * @tparam Args Types of arguments to use to initialize the process.
       * @param args Parameters to use to initialize the process.
       * @return An opaque object to use to concatenate processes.
       */
      template<typename Proc, typename... Args>
      auto attach(Args &&...args) {
          static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
          auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
          auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr});
          // forces the process to exit the uninitialized state
          ref.update(*this, handlers.size() - 1u, {}, nullptr);
          return continuation{&handlers.back()};
      }
  
      /**
       * @brief Schedules a process for the next tick.
       *
       * A process can be either a lambda or a functor. The scheduler wraps both
       * of them in a process adaptor internally.<br/>
       * The signature of the function call operator should be equivalent to the
       * following:
       *
       * @code{.cpp}
       * void(Delta delta, void *data, auto succeed, auto fail);
       * @endcode
       *
       * Where:
       *
       * * `delta` is the elapsed time.
       * * `data` is an opaque pointer to user data if any, `nullptr` otherwise.
       * * `succeed` is a function to call when a process terminates with success.
       * * `fail` is a function to call when a process terminates with errors.
       *
       * The signature of the function call operator of both `succeed` and `fail`
       * is equivalent to the following:
       *
       * @code{.cpp}
       * void();
       * @endcode
       *
       * Returned value is an opaque object that can be used to attach a child to
       * the given process. The child is automatically scheduled when the process
       * terminates and only if the process returns with success.
       *
       * Example of use (pseudocode):
       *
       * @code{.cpp}
       * // schedules a task in the form of a lambda function
       * scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
       *     // code
       * })
       * // appends a child in the form of another lambda function
       * .then([](auto delta, void *, auto succeed, auto fail) {
       *     // code
       * })
       * // appends a child in the form of a process class
       * .then<my_process>(arguments...);
       * @endcode
       *
       * @sa process_adaptor
       *
       * @tparam Func Type of process to schedule.
       * @param func Either a lambda or a functor to use as a process.
       * @return An opaque object to use to concatenate processes.
       */
      template<typename Func>
      auto attach(Func &&func) {
          using Proc = process_adaptor<std::decay_t<Func>, Delta>;
          return attach<Proc>(std::forward<Func>(func));
      }
  
      /**
       * @brief Updates all scheduled processes.
       *
       * All scheduled processes are executed in no specific order.<br/>
       * If a process terminates with success, it's replaced with its child, if
       * any. Otherwise, if a process terminates with an error, it's removed along
       * with its child.
       *
       * @param delta Elapsed time.
       * @param data Optional data.
       */
      void update(const Delta delta, void *data = nullptr) {
          for(auto pos = handlers.size(); pos; --pos) {
              const auto curr = pos - 1u;
  
              if(const auto dead = handlers[curr].update(*this, curr, delta, data); dead) {
                  std::swap(handlers[curr], handlers.back());
                  handlers.pop_back();
              }
          }
      }
  
      /**
       * @brief Aborts all scheduled processes.
       *
       * Unless an immediate operation is requested, the abort is scheduled for
       * the next tick. Processes won't be executed anymore in any case.<br/>
       * Once a process is fully aborted and thus finished, it's discarded along
       * with its child, if any.
       *
       * @param immediately Requests an immediate operation.
       */
      void abort(const bool immediately = false) {
          for(auto pos = handlers.size(); pos; --pos) {
              const auto curr = pos - 1u;
              handlers[curr].abort(*this, curr, immediately);
          }
      }
  
  private:
      std::vector<process_handler> handlers{};
  };
  
  } // namespace entt
  
  #endif
  