add settings reset
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
#ifndef ENTT_PROCESS_FWD_HPP
|
||||
#define ENTT_PROCESS_FWD_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace entt {
|
||||
|
||||
template<typename, typename>
|
||||
class process;
|
||||
|
||||
template<typename = std::uint32_t, typename = std::allocator<void>>
|
||||
class basic_scheduler;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using scheduler = basic_scheduler<>;
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,332 @@
|
||||
#ifndef ENTT_PROCESS_PROCESS_HPP
|
||||
#define ENTT_PROCESS_PROCESS_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "fwd.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/**
|
||||
* @brief Base class for processes.
|
||||
*
|
||||
* This class stays true to the CRTP idiom. Derived classes must specify what's
|
||||
* the intended type for elapsed times.<br/>
|
||||
* A process should expose publicly the following member functions whether
|
||||
* required:
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void update(Delta, void *);
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked once per tick until a process is explicitly aborted or it
|
||||
* terminates either with or without errors. Even though it's not mandatory to
|
||||
* declare this member function, as a rule of thumb each process should at
|
||||
* least define it to work properly. The `void *` parameter is an opaque
|
||||
* pointer to user data (if any) forwarded directly to the process during an
|
||||
* update.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void init();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked when the process joins the running queue of a scheduler. This
|
||||
* happens as soon as it's attached to the scheduler if the process is a top
|
||||
* level one, otherwise when it replaces its parent if the process is a
|
||||
* continuation.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void succeeded();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked in case of success, immediately after an update and during the
|
||||
* same tick.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void failed();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked in case of errors, immediately after an update and during the
|
||||
* same tick.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void aborted();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked only if a process is explicitly aborted. There is no guarantee
|
||||
* that it executes in the same tick, this depends solely on whether the
|
||||
* process is aborted immediately or not.
|
||||
*
|
||||
* Derived classes can change the internal state of a process by invoking the
|
||||
* `succeed` and `fail` protected member functions and even pause or unpause the
|
||||
* process itself.
|
||||
*
|
||||
* @sa scheduler
|
||||
*
|
||||
* @tparam Derived Actual type of process that extends the class template.
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Derived, typename Delta>
|
||||
class process {
|
||||
enum class state : std::uint8_t {
|
||||
uninitialized = 0,
|
||||
running,
|
||||
paused,
|
||||
succeeded,
|
||||
failed,
|
||||
aborted,
|
||||
finished,
|
||||
rejected
|
||||
};
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto next(std::integral_constant<state, state::uninitialized>)
|
||||
-> decltype(std::declval<Target>().init(), void()) {
|
||||
static_cast<Target *>(this)->init();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto next(std::integral_constant<state, state::running>, Delta delta, void *data)
|
||||
-> decltype(std::declval<Target>().update(delta, data), void()) {
|
||||
static_cast<Target *>(this)->update(delta, data);
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto next(std::integral_constant<state, state::succeeded>)
|
||||
-> decltype(std::declval<Target>().succeeded(), void()) {
|
||||
static_cast<Target *>(this)->succeeded();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto next(std::integral_constant<state, state::failed>)
|
||||
-> decltype(std::declval<Target>().failed(), void()) {
|
||||
static_cast<Target *>(this)->failed();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto next(std::integral_constant<state, state::aborted>)
|
||||
-> decltype(std::declval<Target>().aborted(), void()) {
|
||||
static_cast<Target *>(this)->aborted();
|
||||
}
|
||||
|
||||
void next(...) const noexcept {}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Terminates a process with success if it's still alive.
|
||||
*
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* alive.
|
||||
*/
|
||||
void succeed() noexcept {
|
||||
if(alive()) {
|
||||
current = state::succeeded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Terminates a process with errors if it's still alive.
|
||||
*
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* alive.
|
||||
*/
|
||||
void fail() noexcept {
|
||||
if(alive()) {
|
||||
current = state::failed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stops a process if it's in a running state.
|
||||
*
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* running.
|
||||
*/
|
||||
void pause() noexcept {
|
||||
if(current == state::running) {
|
||||
current = state::paused;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restarts a process if it's paused.
|
||||
*
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* paused.
|
||||
*/
|
||||
void unpause() noexcept {
|
||||
if(current == state::paused) {
|
||||
current = state::running;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Type used to provide elapsed time. */
|
||||
using delta_type = Delta;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~process() noexcept {
|
||||
static_assert(std::is_base_of_v<process, Derived>, "Incorrect use of the class template");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Aborts a process if it's still alive.
|
||||
*
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* alive.
|
||||
*
|
||||
* @param immediate Requests an immediate operation.
|
||||
*/
|
||||
void abort(const bool immediate = false) {
|
||||
if(alive()) {
|
||||
current = state::aborted;
|
||||
|
||||
if(immediate) {
|
||||
tick({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process is either running or paused.
|
||||
* @return True if the process is still alive, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool alive() const noexcept {
|
||||
return current == state::running || current == state::paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process is already terminated.
|
||||
* @return True if the process is terminated, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool finished() const noexcept {
|
||||
return current == state::finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process is currently paused.
|
||||
* @return True if the process is paused, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool paused() const noexcept {
|
||||
return current == state::paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process terminated with errors.
|
||||
* @return True if the process terminated with errors, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool rejected() const noexcept {
|
||||
return current == state::rejected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates a process and its internal state if required.
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void tick(const Delta delta, void *data = nullptr) {
|
||||
switch(current) {
|
||||
case state::uninitialized:
|
||||
next(std::integral_constant<state, state::uninitialized>{});
|
||||
current = state::running;
|
||||
break;
|
||||
case state::running:
|
||||
next(std::integral_constant<state, state::running>{}, delta, data);
|
||||
break;
|
||||
default:
|
||||
// suppress warnings
|
||||
break;
|
||||
}
|
||||
|
||||
// if it's dead, it must be notified and removed immediately
|
||||
switch(current) {
|
||||
case state::succeeded:
|
||||
next(std::integral_constant<state, state::succeeded>{});
|
||||
current = state::finished;
|
||||
break;
|
||||
case state::failed:
|
||||
next(std::integral_constant<state, state::failed>{});
|
||||
current = state::rejected;
|
||||
break;
|
||||
case state::aborted:
|
||||
next(std::integral_constant<state, state::aborted>{});
|
||||
current = state::rejected;
|
||||
break;
|
||||
default:
|
||||
// suppress warnings
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
state current{state::uninitialized};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Adaptor for lambdas and functors to turn them into processes.
|
||||
*
|
||||
* Lambdas and functors can't be used directly with a scheduler for they are not
|
||||
* properly defined processes with managed life cycles.<br/>
|
||||
* This class helps in filling the gap and turning lambdas and functors into
|
||||
* full featured processes usable by a scheduler.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Usually users shouldn't worry about creating adaptors. A scheduler will
|
||||
* create them internally each and avery time a lambda or a functor is used as
|
||||
* a process.
|
||||
*
|
||||
* @sa process
|
||||
* @sa scheduler
|
||||
*
|
||||
* @tparam Func Actual type of process.
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Func, typename Delta>
|
||||
struct process_adaptor: process<process_adaptor<Func, Delta>, Delta>, private Func {
|
||||
/**
|
||||
* @brief Constructs a process adaptor from a lambda or a functor.
|
||||
* @tparam Args Types of arguments to use to initialize the actual process.
|
||||
* @param args Parameters to use to initialize the actual process.
|
||||
*/
|
||||
template<typename... Args>
|
||||
process_adaptor(Args &&...args)
|
||||
: Func{std::forward<Args>(args)...} {}
|
||||
|
||||
/**
|
||||
* @brief Updates a process and its internal state if required.
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void update(const Delta delta, void *data) {
|
||||
Func::operator()(
|
||||
delta,
|
||||
data,
|
||||
[this]() { this->succeed(); },
|
||||
[this]() { this->fail(); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,352 @@
|
||||
#ifndef ENTT_PROCESS_SCHEDULER_HPP
|
||||
#define ENTT_PROCESS_SCHEDULER_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "../config/config.h"
|
||||
#include "../core/compressed_pair.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "process.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/*! @cond TURN_OFF_DOXYGEN */
|
||||
namespace internal {
|
||||
|
||||
template<typename Delta>
|
||||
struct basic_process_handler {
|
||||
virtual ~basic_process_handler() = default;
|
||||
|
||||
virtual bool update(const Delta, void *) = 0;
|
||||
virtual void abort(const bool) = 0;
|
||||
|
||||
// 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>
|
||||
struct process_handler final: basic_process_handler<Delta> {
|
||||
template<typename... Args>
|
||||
process_handler(Args &&...args)
|
||||
: process{std::forward<Args>(args)...} {}
|
||||
|
||||
bool update(const Delta delta, void *data) override {
|
||||
if(process.tick(delta, data); process.rejected()) {
|
||||
this->next.reset();
|
||||
}
|
||||
|
||||
return (process.rejected() || process.finished());
|
||||
}
|
||||
|
||||
void abort(const bool immediate) override {
|
||||
process.abort(immediate);
|
||||
}
|
||||
|
||||
Type process;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
/*! @endcond */
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Delta, typename Allocator>
|
||||
class basic_scheduler {
|
||||
template<typename Type>
|
||||
using handler_type = internal::process_handler<Delta, Type>;
|
||||
|
||||
// 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:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using delta_type = Delta;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_scheduler()
|
||||
: 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 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 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.
|
||||
* @return Number of processes currently scheduled.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const noexcept {
|
||||
return handlers.first().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.first().empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Discards all scheduled processes.
|
||||
*
|
||||
* Processes aren't aborted. They are discarded along with their children
|
||||
* and never executed again.
|
||||
*/
|
||||
void clear() {
|
||||
handlers.first().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Schedules a process for the next tick.
|
||||
*
|
||||
* Returned value can be used to attach a continuation for the last process.
|
||||
* The continutation is scheduled automatically 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 This process scheduler.
|
||||
*/
|
||||
template<typename Proc, typename... Args>
|
||||
basic_scheduler &attach(Args &&...args) {
|
||||
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
|
||||
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
|
||||
ref->update({}, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 can be used to attach a continuation for the last process.
|
||||
* The continutation is scheduled automatically 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 This process scheduler.
|
||||
*/
|
||||
template<typename Func>
|
||||
basic_scheduler &attach(Func &&func) {
|
||||
using Proc = process_adaptor<std::decay_t<Func>, Delta>;
|
||||
return attach<Proc>(std::forward<Func>(func));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a process as a continuation of the last scheduled process.
|
||||
* @tparam Proc Type of process to use as a continuation.
|
||||
* @tparam Args Types of arguments to use to initialize the process.
|
||||
* @param args Parameters to use to initialize the process.
|
||||
* @return This process scheduler.
|
||||
*/
|
||||
template<typename Proc, typename... Args>
|
||||
basic_scheduler &then(Args &&...args) {
|
||||
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
|
||||
ENTT_ASSERT(!handlers.first().empty(), "Process not available");
|
||||
auto *curr = handlers.first().back().get();
|
||||
for(; curr->next; curr = curr->next.get()) {}
|
||||
curr->next = std::allocate_shared<handler_type<Proc>>(handlers.second(), std::forward<Args>(args)...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a process as a continuation of the last scheduled process.
|
||||
* @tparam Func Type of process to use as a continuation.
|
||||
* @param func Either a lambda or a functor to use as a process.
|
||||
* @return This process scheduler.
|
||||
*/
|
||||
template<typename Func>
|
||||
basic_scheduler &then(Func &&func) {
|
||||
using Proc = process_adaptor<std::decay_t<Func>, Delta>;
|
||||
return then<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_type delta, void *data = nullptr) {
|
||||
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
|
||||
if(auto &curr = handlers.first()[pos]; curr->next) {
|
||||
curr = std::move(curr->next);
|
||||
// forces the process to exit the uninitialized state
|
||||
curr->update({}, nullptr);
|
||||
} else {
|
||||
curr = std::move(handlers.first().back());
|
||||
handlers.first().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 immediate Requests an immediate operation.
|
||||
*/
|
||||
void abort(const bool immediate = false) {
|
||||
for(auto &&curr: handlers.first()) {
|
||||
curr->abort(immediate);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
compressed_pair<container_type, allocator_type> handlers;
|
||||
};
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user