diff --git a/loader/include/Geode/utils/Promise.hpp b/loader/include/Geode/utils/Promise.hpp index d09b200c..323e3ae2 100644 --- a/loader/include/Geode/utils/Promise.hpp +++ b/loader/include/Geode/utils/Promise.hpp @@ -9,9 +9,26 @@ namespace geode { struct DefaultProgress { std::string message; std::optional percentage; + + DefaultProgress() = default; + DefaultProgress(auto msg) : message(msg) {} + DefaultProgress(auto msg, uint8_t percentage) : message(msg), percentage(percentage) {} }; } + class PromiseCancellationToken final { + private: + std::shared_ptr token = std::make_shared(false); + + template + friend class Promise; + + public: + inline operator bool() { + return *token; + } + }; + template class PromiseEventFilter; @@ -29,82 +46,57 @@ namespace geode { using Expect = utils::MiniFunction; using Progress = utils::MiniFunction; using Finally = utils::MiniFunction; + using SimpleExecutor = utils::MiniFunction; + using Executor = 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 + * overloads to specify progress and handle cancellation. + * + * @param threaded Whether the Promise should start executing in a new + * thread or not; if false, the Promise starts immediately executing in + * the current thread + * + * @note 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); - }) {} + Promise(SimpleExecutor&& executor, bool threaded = true) + : Promise([executor](auto resolve, auto reject, auto, auto) { + executor(resolve, reject); + }, threaded) {} /** * 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 + * and not call any of the other callbacks. + * + * @param threaded Whether the Promise should start executing in a new + * thread or not; if false, the Promise starts immediately executing in + * the current thread + * + * @note 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 - ); + Promise(Executor&& executor, bool threaded = true) : m_data(std::make_shared()) { + if (threaded) { + std::thread([executor = std::move(executor), data = m_data]() mutable { + Promise::invoke_executor(std::move(executor), data); + }).detach(); + } + else { + Promise::invoke_executor(std::move(executor), m_data); + } } /** @@ -112,13 +104,13 @@ namespace geode { * 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; + Promise& then(Then&& handler) { + if (m_data->cancellationToken) 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()] { + if (v.index() == 0) { + Loader::get()->queueInMainThread([handler = std::move(handler), ok = std::move(std::get<0>(v))] { handler(ok); }); } @@ -133,13 +125,13 @@ namespace geode { * 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; + Promise& expect(Expect&& handler) { + if (m_data->cancellationToken) 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()] { + if (v.index() == 1) { + Loader::get()->queueInMainThread([handler = std::move(handler), err = std::move(std::get<1>(v))] { handler(err); }); } @@ -154,8 +146,8 @@ namespace geode { * 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; + Promise& progress(Progress&& handler) { + if (m_data->cancellationToken) return *this; std::unique_lock _(m_data->mutex); if (!m_data->result.has_value()) { m_data->progressHandler = handler; @@ -168,8 +160,8 @@ namespace geode { * 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; + Promise& finally(Finally&& handler) { + if (m_data->cancellationToken) return *this; std::unique_lock _(m_data->mutex); if (m_data->result.has_value()) { Loader::get()->queueInMainThread([handler = std::move(handler)] { @@ -191,16 +183,25 @@ namespace geode { * cancel */ Promise& cancel() { - if (m_data->cancelled) return *this; + if (m_data->cancellationToken) 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; + m_data->cancellationToken.token = true; return *this; } - + + /** + * Link this Promise to be cancelled alongside another Promise + */ + Promise& link(PromiseCancellationToken otherCancellationToken) { + if (m_data->cancellationToken) return *this; + m_data->cancellationToken = otherCancellationToken; + return *this; + } + /** * Returns a filter for listening to this `Promise` through the Geode * Events system. Useful for example for using `Promise`s on layers, @@ -218,12 +219,67 @@ namespace geode { Expect expectHandler; Progress progressHandler; Finally finallyHandler; - std::optional> result; - std::atomic_bool cancelled = false; + std::optional> result; + PromiseCancellationToken cancellationToken; }; // 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; + + static void invoke_executor(Executor&& executor, std::shared_ptr data) { + executor( + [data](auto&& value) { + if (data->cancellationToken) 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 = std::variant(std::in_place_index<0>, std::move(value)); + } + }, + [data](auto&& error) { + if (data->cancellationToken) 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 = std::variant(std::in_place_index<1>, std::move(error)); + } + }, + [data](auto&& p) { + if (data->cancellationToken) return; + std::unique_lock _(data->mutex); + if (auto handler = data->progressHandler) { + Loader::get()->queueInMainThread([p = std::move(p), handler]() mutable { + handler(std::move(p)); + }); + } + }, + data->cancellationToken + ); + } }; /**