mirror of
https://github.com/quizhizhe/LiteLoaderBDS-1.16.40.git
synced 2025-06-05 03:43:40 +00:00
284 lines
9.5 KiB
C++
284 lines
9.5 KiB
C++
#ifndef ENTT_PROCESS_SCHEDULER_HPP
|
|
#define ENTT_PROCESS_SCHEDULER_HPP
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
#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) 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()
|
|
: handlers{} {}
|
|
|
|
/*! @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 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 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
|