#pragma once #include "Result.hpp" #include "MiniFunction.hpp" #include "../loader/Event.hpp" namespace geode { namespace impl { struct DefaultProgress { std::string message; std::optional percentage; DefaultProgress() = default; DefaultProgress(std::string const& msg) : message(msg) {} DefaultProgress(auto msg, uint8_t percentage) : message(msg), percentage(percentage) {} }; } struct CancelledState final {}; template class PromiseEventFilter; template class Promise final { public: using Value = T; using Error = E; using Progress = P; using OnResolved = utils::MiniFunction; using OnRejected = utils::MiniFunction; using OnProgress = utils::MiniFunction; using OnFinished = utils::MiniFunction; using OnCancelled = utils::MiniFunction; class State final { private: std::variant m_value; template State(std::in_place_index_t index, V&& value) : m_value(index, std::forward(value)) {} public: static State make_value(Value&& value) { return State(std::in_place_index<0>, std::move(value)); } static State make_error(Error&& error) { return State(std::in_place_index<1>, std::move(error)); } static State make_progress(Progress&& progress) { return State(std::in_place_index<2>, std::move(progress)); } static State make_cancelled() { return State(std::in_place_index<3>, CancelledState()); } template typename Promise::State convert() && { if (this->has_value()) { if constexpr (std::is_same_v) { return Promise::State::make_value(std::move(std::move(*this).take_value())); } log::error("THIS CODE PATH SHOULD BE UNREACHABLE!!!!"); } if (this->has_error()) { if constexpr (std::is_same_v) { return Promise::State::make_error(std::move(std::move(*this).take_error())); } log::error("THIS CODE PATH SHOULD BE UNREACHABLE!!!!"); } if (this->has_progress()) { if constexpr (std::is_same_v) { return Promise::State::make_progress(std::move(std::move(*this).take_progress())); } log::error("THIS CODE PATH SHOULD BE UNREACHABLE!!!!"); } return Promise::State::make_cancelled(); } bool has_value() { return m_value.index() == 0; } Value get_value() const { return std::get<0>(m_value); } Value take_value() && { return std::get<0>(std::move(m_value)); } bool has_error() { return m_value.index() == 1; } Error get_error() const { return std::get<1>(m_value); } Error take_error() && { return std::get<1>(std::move(m_value)); } bool has_progress() { return m_value.index() == 2; } Progress get_progress() const { return std::get<2>(m_value); } Progress take_progress() && { return std::get<2>(std::move(m_value)); } bool is_cancelled() { return m_value.index() == 3; } }; using OnStateChange = utils::MiniFunction; Promise() : m_data(std::make_shared()) {} Promise(utils::MiniFunction source, bool threaded = true) : Promise([source](auto resolve, auto reject, auto, auto const&) { source(resolve, reject); }, threaded) {} Promise(utils::MiniFunction source, bool threaded = true) : Promise([source](auto onStateChanged, auto const& cancelled) { source( [onStateChanged](auto&& value) { onStateChanged(State::make_value(std::move(value))); }, [onStateChanged](auto&& error) { onStateChanged(State::make_error(std::move(error))); }, [onStateChanged](auto&& progress) { onStateChanged(State::make_progress(std::move(progress))); }, cancelled ); }, threaded, std::monostate()) {} Promise(utils::MiniFunction source, bool threaded, std::monostate tag) : m_data(std::make_shared()) { m_data->shouldStartThreaded = threaded; if (threaded) { std::thread([source = std::move(source), data = m_data]() mutable { Promise::invoke_source(std::move(source), data); }).detach(); } else { Promise::invoke_source(std::move(source), m_data); } } Promise then(utils::MiniFunction&& callback) { return make_fwd( [callback](typename Promise::State&& state) -> typename Promise::State { if (state.has_value()) { callback(state.get_value()); } return std::move(state); }, m_data ); } template requires (!std::is_void_v) Promise then(utils::MiniFunction&& callback) { return make_fwd( [callback](typename Promise::State&& state) -> typename Promise::State { if (state.has_value()) { return Promise::State::make_value(callback(std::move(state).take_value())); } return std::move(state).template convert(); }, m_data ); } template requires (!std::is_void_v) Promise then(utils::MiniFunction(Result)>&& callback) { return make_fwd( [callback](typename Promise::State&& state) -> typename Promise::State { if (state.has_value() || state.has_error()) { auto current = state.has_value() ? Result(Ok(std::move(state).take_value())) : Result(Err(std::move(state).take_error())); auto result = callback(std::move(current)); if (result.isOk()) { return Promise::State::make_value(std::move(std::move(result).unwrap())); } else { return Promise::State::make_error(std::move(std::move(result).unwrapErr())); } } return std::move(state).template convert(); }, m_data ); } Promise expect(utils::MiniFunction&& callback) { return make_fwd( [callback](typename Promise::State&& state) -> typename Promise::State { if (state.has_error()) { callback(state.get_error()); } return std::move(state); }, m_data ); } template requires (!std::is_void_v) Promise expect(utils::MiniFunction&& callback) { return make_fwd( [callback](typename Promise::State&& state) -> typename Promise::State { if (state.has_error()) { return Promise::State::make_error(callback(std::move(state).take_error())); } return std::move(state).template convert(); }, m_data ); } Promise progress(utils::MiniFunction&& callback) { return make_fwd( [callback](typename Promise::State&& state) -> typename Promise::State { if (state.has_progress()) { callback(state.get_progress()); } return std::move(state); }, m_data ); } template requires (!std::is_void_v) Promise progress(utils::MiniFunction&& callback) { return make_fwd( [callback](typename Promise::State&& state) -> typename Promise::State { if (state.has_progress()) { return Promise::State::make_progress(callback(std::move(state).take_progress())); } return std::move(state).template convert(); }, m_data ); } Promise finally(utils::MiniFunction&& callback) { return make_fwd( [callback](typename Promise::State&& state) -> typename Promise::State { if (state.has_value() || state.has_error()) { callback(); } return std::move(state); }, m_data ); } Promise cancelled(utils::MiniFunction&& callback) { return make_fwd( [callback](typename Promise::State&& state) -> typename Promise::State { if (state.is_cancelled()) { callback(); } return std::move(state); }, m_data ); } void resolve(Value&& value) { invoke_callback(State::make_value(std::move(value)), m_data); } void reject(Error&& error) { invoke_callback(State::make_error(std::move(error)), m_data); } void cancel() { m_data->cancelled = true; invoke_callback(State::make_cancelled(), m_data); } /** * Returns a filter for listening to this `Promise` through the Geode * Events system. Useful for example for using `Promise`s on layers, * which may be removed from the node tree before the `Promise` * finishes and as such calling a `then` callback that captures the * layer would then read undefined memory */ PromiseEventFilter listen(); private: // I'm not sure just how un-performant this is, although then again you // should not be using Promises in performance-sensitive code since the // whole point of them is to wait for stuff that happens in the // possibly distant future struct Data final { std::mutex mutex; std::vector callbacks; std::optional> result; std::atomic_bool cancelled; std::atomic_bool shouldStartThreaded; }; std::shared_ptr m_data; template static Promise make_fwd( auto&& transformState, std::shared_ptr data ) { return Promise([data, transformState](auto fwdStateToNextPromise, auto const&) { Promise::set_callback( [fwdStateToNextPromise, transformState](auto&& state) { // Map the state auto mapped = transformState(std::move(state)); // Forward the value to the next Promise fwdStateToNextPromise(std::move(mapped)); }, data ); }, data->shouldStartThreaded, std::monostate()); } static void set_callback(OnStateChange&& callback, std::shared_ptr data) { std::unique_lock lock(data->mutex); data->callbacks.emplace_back(std::move(callback)); // Check if the callback should be immediately fired because // the Promise is already resolved or cancelled if (data->cancelled) { invoke_callback_no_lock(State::make_cancelled(), data); } if (data->result) { if (data->result->index() == 0) { invoke_callback_no_lock(State::make_value(Value(std::get<0>(*data->result))), data); } else { invoke_callback_no_lock(State::make_error(Error(std::get<1>(*data->result))), data); } } } static void invoke_callback(State&& state, std::shared_ptr data) { std::unique_lock lock(data->mutex); invoke_callback_no_lock(std::move(state), data); } static void invoke_callback_no_lock(State&& state, std::shared_ptr data) { // Run callbacks in the main thread Loader::get()->queueInMainThread([callbacks = data->callbacks, state = State(state)]() { for (auto&& callback : std::move(callbacks)) { callback(state); } }); // Store the state to let future installed callbacks be immediately resolved if (state.has_value()) { data->result = std::variant(std::in_place_index<0>, std::move(state).take_value()); } else if (state.has_error()) { data->result = std::variant(std::in_place_index<1>, std::move(state).take_error()); } else if (state.is_cancelled()) { data->cancelled = true; } } static void invoke_source(utils::MiniFunction&& source, std::shared_ptr data) { source( [data](auto&& state) { invoke_callback(std::move(state), data); }, data->cancelled ); } }; /** * Wraps a `Promise` in the Geode Event system for easier consumption. * Useful for example for layers, where just regularly waiting for the * `Promise` could run into issues if the layer is freed from memory; * whereas with event listeners being RAII, they are automatically * removed from layers, avoiding use-after-free errors */ template class PromiseEvent : public Event { protected: size_t m_id; std::variant m_value; PromiseEvent(size_t id, std::variant&& value) : m_id(id), m_value(value) {} friend class Promise; friend class PromiseEventFilter; public: T const* getResolve() const { return std::get_if<0>(&m_value); } E const* getReject() const { return std::get_if<1>(&m_value); } P const* getProgress() const { return std::get_if<2>(&m_value); } bool isFinally() const { return m_value.index() != 2; } }; template class PromiseEventFilter : public EventFilter> { public: using Callback = void(PromiseEvent*); protected: size_t m_id; friend class Promise; PromiseEventFilter(size_t id) : m_id(id) {} public: PromiseEventFilter() : m_id(0) {} ListenerResult handle(utils::MiniFunction fn, PromiseEvent* event) { // log::debug("Event mod filter: {}, {}, {}, {}", m_mod, static_cast(m_type), event->getMod(), static_cast(event->getType())); if (m_id == event->m_id) { fn(event); } return ListenerResult::Propagate; } }; template PromiseEventFilter Promise::listen() { // After 4 billion promises this will overflow and start producing // the same IDs again, so technically if some promise takes // literally forever then this could cause issues later on static size_t ID_COUNTER = 0; ID_COUNTER += 1; // Reserve 0 for PromiseEventFilter not listening to anything if (ID_COUNTER == 0) { ID_COUNTER += 1; } size_t id = ID_COUNTER; this ->then([id](auto&& value) { PromiseEvent(id, std::variant { std::in_place_index<0>, std::forward(value) }).post(); }) .expect([id](auto&& error) { PromiseEvent(id, std::variant { std::in_place_index<1>, std::forward(error) }).post(); }) .progress([id](auto&& prog) { PromiseEvent(id, std::variant { std::in_place_index<2>, std::forward

(prog) }).post(); }); return PromiseEventFilter(id); } }