summaryrefslogtreecommitdiffstats
path: root/deps/include/entt/process
diff options
context:
space:
mode:
authoruntodesu <kirill@untode.su>2025-03-15 16:22:09 +0500
committeruntodesu <kirill@untode.su>2025-03-15 16:22:09 +0500
commit3bf42c6ff3805a0d42bbc661794a95ff31bedc26 (patch)
tree05049955847504808d6bed2bb7b155f8b03807bb /deps/include/entt/process
parent02294547dcde0d4ad76e229106702261e9f10a51 (diff)
downloadvoxelius-3bf42c6ff3805a0d42bbc661794a95ff31bedc26.tar.bz2
voxelius-3bf42c6ff3805a0d42bbc661794a95ff31bedc26.zip
Add whatever I was working on for the last month
Diffstat (limited to 'deps/include/entt/process')
-rw-r--r--deps/include/entt/process/fwd.hpp20
-rw-r--r--deps/include/entt/process/process.hpp354
-rw-r--r--deps/include/entt/process/scheduler.hpp364
3 files changed, 738 insertions, 0 deletions
diff --git a/deps/include/entt/process/fwd.hpp b/deps/include/entt/process/fwd.hpp
new file mode 100644
index 0000000..18847e7
--- /dev/null
+++ b/deps/include/entt/process/fwd.hpp
@@ -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
diff --git a/deps/include/entt/process/process.hpp b/deps/include/entt/process/process.hpp
new file mode 100644
index 0000000..f2692bd
--- /dev/null
+++ b/deps/include/entt/process/process.hpp
@@ -0,0 +1,354 @@
+#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();
+ }
+
+ template<typename... Args>
+ void next(Args &&...) 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 constructor. */
+ constexpr process() = default;
+
+ /*! @brief Default copy constructor. */
+ process(const process &) = default;
+
+ /*! @brief Default move constructor. */
+ process(process &&) noexcept = default;
+
+ /**
+ * @brief Default copy assignment operator.
+ * @return This process.
+ */
+ process &operator=(const process &) = default;
+
+ /**
+ * @brief Default move assignment operator.
+ * @return This process.
+ */
+ process &operator=(process &&) noexcept = default;
+
+ /*! @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
diff --git a/deps/include/entt/process/scheduler.hpp b/deps/include/entt/process/scheduler.hpp
new file mode 100644
index 0000000..6cd27db
--- /dev/null
+++ b/deps/include/entt/process/scheduler.hpp
@@ -0,0 +1,364 @@
+#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() noexcept = 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 Default copy constructor, deleted on purpose. */
+ basic_scheduler(const basic_scheduler &) = delete;
+
+ /**
+ * @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)
+ : handlers{container_type{std::move(other.handlers.first()), allocator}, allocator} {
+ ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a scheduler is not allowed");
+ }
+
+ /*! @brief Default destructor. */
+ ~basic_scheduler() noexcept = default;
+
+ /**
+ * @brief Default copy assignment operator, deleted on purpose.
+ * @return This process scheduler.
+ */
+ basic_scheduler &operator=(const basic_scheduler &) = delete;
+
+ /**
+ * @brief Move assignment operator.
+ * @param other The instance to move from.
+ * @return This process scheduler.
+ */
+ basic_scheduler &operator=(basic_scheduler &&other) noexcept {
+ ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "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