#pragma once #include "Result.hpp" #include "MiniFunction.hpp" #include "../loader/Event.hpp" namespace geode { namespace impl { struct DefaultProgress { std::string message; std::optional percentage; }; } template class PromiseEventFilter; /** * Represents an asynchronous `Result`. Similar to `Future` in Rust, or * `Promise` in JavaScript. May have only one of each kind of callback. Can * also be used to monitor the progress of the upcoming value. All * callbacks are always run in the main thread, so interacting with UI is * safe */ template class [[nodiscard]] Promise final { public: using Then = utils::MiniFunction; using Expect = utils::MiniFunction; using Progress = utils::MiniFunction; using Finally = utils::MiniFunction; /** * Create a Promise. Call the provided callbacks to notify the * listener when the Promise is finished. Use the other constructor * overloads to specify progress and handle cancellation. See the * class description for general information about Promises */ Promise(utils::MiniFunction&& create) : Promise([create](auto resolve, auto reject, auto, auto const&) { create(resolve, reject); }) {} /** * Create a Promise. Call the provided callbacks to notify the * listener when the Promise is finished. If the user cancels the * Promise, this is reflected in the `cancelled` parameter; you can * read from it, and if it's true, you can stop whatever you were doing * and not call any of the other callbacks. See the class description * for general information about Promises */ Promise(utils::MiniFunction&& create) : m_data(std::make_unique()) { create( [data = m_data](auto&& value) { if (data->cancelled) return; std::unique_lock _(data->mutex); bool handled = false; if (data->thenHandler) { Loader::get()->queueInMainThread([fun = std::move(data->thenHandler), v = std::move(value)] { fun(v); }); handled = true; } if (data->finallyHandler) { Loader::get()->queueInMainThread([fun = std::move(data->finallyHandler)] { fun(); }); handled = true; } if (!handled) { data->result = Ok(std::move(value)); } }, [data = m_data](auto&& error) { if (data->cancelled) return; std::unique_lock _(data->mutex); bool handled = false; if (data->expectHandler) { Loader::get()->queueInMainThread([fun = std::move(data->expectHandler), v = std::move(error)] { fun(v); }); handled = true; } if (data->finallyHandler) { Loader::get()->queueInMainThread([fun = std::move(data->finallyHandler)] { fun(); }); handled = true; } if (!handled) { data->result = Err(std::move(error)); } }, [data = m_data](auto&& p) { if (data->cancelled) return; std::unique_lock _(data->mutex); if (auto handler = data->progressHandler) { handler(std::move(p)); } }, m_data->cancelled ); } /** * Add a listener for when the Promise finishes. There may only be one * listener at a time. If the Promise has already been resolved, the * callback is immediately queued in the main thread */ Promise& then(Then handler) { if (m_data->cancelled) return *this; std::unique_lock _(m_data->mutex); if (m_data->result.has_value()) { auto v = std::move(m_data->result).value(); if (v.isOk()) { Loader::get()->queueInMainThread([handler = std::move(handler), ok = std::move(v).unwrap()] { handler(ok); }); } } else { m_data->thenHandler = handler; } return *this; } /** * Add a listener for when the Promise fails. There may only be one * listener at a time. If the Promise has already been resolved, the * callback is immediately queued in the main thread */ Promise& expect(Expect handler) { if (m_data->cancelled) return *this; std::unique_lock _(m_data->mutex); if (m_data->result.has_value()) { auto v = std::move(m_data->result).value(); if (v.isErr()) { Loader::get()->queueInMainThread([handler = std::move(handler), err = std::move(v).unwrapErr()] { handler(err); }); } } else { m_data->expectHandler = handler; } return *this; } /** * Add a listener for when the Promise's progress is updated. There may * only be one listener at a time. If the Promise has already been * resolved, nothing happens */ Promise& progress(Progress handler) { if (m_data->cancelled) return *this; std::unique_lock _(m_data->mutex); if (!m_data->result.has_value()) { m_data->progressHandler = handler; } return *this; } /** * Add a listener for when the Promise is finished, regardless of if * it was succesful or not. There may only be one listener at a time. * If the Promise has already been resolved, the callback is * immediately queued in the main thread */ Promise& finally(Finally handler) { if (m_data->cancelled) return *this; std::unique_lock _(m_data->mutex); if (m_data->result.has_value()) { Loader::get()->queueInMainThread([handler = std::move(handler)] { handler(); }); } else { m_data->finallyHandler = handler; } return *this; } /** * Cancel the Promise. Removes all listeners and sets the signal for * cancelling. Whether or not the promise actually can interrupt its * operation depends on the Promise; as such, this is not guaranteed to * actually stop the operation that created the Promise, but it is * guaranteed that the listener will not be notified after a call to * cancel */ Promise& cancel() { if (m_data->cancelled) return *this; std::unique_lock _(m_data->mutex); m_data->thenHandler = nullptr; m_data->expectHandler = nullptr; m_data->progressHandler = nullptr; m_data->finallyHandler = nullptr; m_data->cancelled = true; return *this; } /** * 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: struct Data { // Mutex for handlers & result std::mutex mutex; Then thenHandler; Expect expectHandler; Progress progressHandler; Finally finallyHandler; std::optional> result; std::atomic_bool cancelled = false; }; // This has to be a shared_ptr so that the data can persist even after // the future is destroyed, as well as to share it between resolve, reject, and the likes std::shared_ptr m_data; }; /** * 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; // 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); } }