diff options
| author | Linus Jahn <lnj@kaidan.im> | 2022-08-16 21:00:15 +0200 |
|---|---|---|
| committer | Linus Jahn <lnj@kaidan.im> | 2023-01-03 22:05:54 +0100 |
| commit | b17284ee7d674416e0d11f1699f73fcc606262d4 (patch) | |
| tree | 86597f2bc2a1ed2d257e0cbf8e7de1ca54080c08 /src/base/QXmppTask.h | |
| parent | 3271c6642439d4d3c0d8c634e2b3f4cf17b908a0 (diff) | |
| download | qxmpp-b17284ee7d674416e0d11f1699f73fcc606262d4.tar.gz | |
Introduce QXmppTask & QXmppPromise
Closes #502.
Co-authored-by: Jonah BrĂ¼chert <jbb@kaidan.im>
Diffstat (limited to 'src/base/QXmppTask.h')
| -rw-r--r-- | src/base/QXmppTask.h | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/src/base/QXmppTask.h b/src/base/QXmppTask.h new file mode 100644 index 00000000..c11557ac --- /dev/null +++ b/src/base/QXmppTask.h @@ -0,0 +1,230 @@ +// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im> +// SPDX-FileCopyrightText: 2022 Jonah BrĂ¼chert <jbb@kaidan.im> +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPTASK_H +#define QXMPPTASK_H + +#include "qxmpp_export.h" + +#include <functional> +#include <memory> +#include <optional> + +#include <QFuture> +#include <QPointer> + +template<typename T> +class QXmppPromise; + +namespace QXmpp::Private { + +struct TaskData; + +class QXMPP_EXPORT TaskPrivate +{ +public: + TaskPrivate(void (*freeResult)(void *)); + ~TaskPrivate(); + + bool isFinished() const; + void setFinished(bool); + bool isContextAlive(); + void setContext(QObject *); + void *result() const; + void setResult(void *); + void resetResult() { setResult(nullptr); } + const std::function<void(TaskPrivate &, void *)> continuation() const; + void setContinuation(std::function<void(TaskPrivate &, void *)> &&); + void invokeContinuation(void *result); + +private: + std::shared_ptr<TaskData> d; +}; + +} // namespace QXmpp::Private + +/// +/// Handle for an ongoing operation that finishes in the future. +/// +/// Tasks are generated by QXmppPromise and can be handled using QXmppTask::then(). +/// +/// It supports abstract types, i.e. you can create a QXmppTask<QXmppStanza> and report MyIq +/// (derived from QXmppStanza) to it and when handling the result it is possible to cast the result +/// back to MyIq. +/// +/// Unlike QFuture, this is not thread-safe. This avoids the need to do mutex locking at every +/// access though. +/// +/// \ingroup Core classes +/// +/// \since QXmpp 1.5 +/// +template<typename T> +class QXmppTask +{ +public: + ~QXmppTask() = default; + + /// + /// Registers a function that will be called with the result as parameter when the asynchronous + /// operation finishes. + /// + /// If the task is already finished when calling this (and still has a result), the function + /// will be called immediately. + /// + /// If another function was previously registered using .then(), the old function will be + /// replaced, and only the new one will be called. + /// + /// Example usage: + /// ``` + /// QXmppTask<QString> generateSomething(); + /// + /// void Manager::generate() + /// { + /// generateSomething().then(this, [](QString &&result) { + /// // runs as soon as the result is finished + /// qDebug() << "Generating finished:" << result; + /// }); + /// + /// // The generation could still be in progress here and the lambda might not + /// // have been executed yet. + /// } + /// + /// // Manager is derived from QObject. + /// ``` + /// + /// \param context QObject used for unregistering the handler function when the object is + /// deleted. This way your lambda will never be executed after your object has been deleted. + /// \param continuation A function accepting a result in the form of `T &&`. + /// +#ifndef QXMPP_DOC + template<typename Continuation> +#endif + void then(QObject *context, Continuation continuation) + { + static_assert( + std::is_void_v<T> && std::is_invocable_v<Continuation> || + !std::is_void_v<T> /* && invocable with T && causes forming ref to void error */, + "Function needs to be invocable with T && or without params for T=void."); + using namespace QXmpp::Private; + + if (d.isFinished()) { + if constexpr (std::is_void_v<T>) { + continuation(); + } else { + // when calling then() after finished value could be empty + if (hasResult()) { + continuation(std::move(*reinterpret_cast<T *>(d.result()))); + d.resetResult(); + } + } + } else { + d.setContext(context); + d.setContinuation([f = std::forward<Continuation>(continuation)](TaskPrivate &d, void *result) mutable { + if (d.isContextAlive()) { + if constexpr (std::is_void_v<T>) { + f(); + } else { + f(std::move(*reinterpret_cast<T *>(result))); + } + } + + // clear continuation to avoid "deadlocks" in case the user captured this QXmppTask + d.setContinuation({}); + }); + } + } + + /// + /// Whether the asynchronous operation is already finished. + /// + /// This does not mean that the result is still stored, it might have been taken using + /// takeResult() or handled using then(). + /// + [[nodiscard]] bool isFinished() const { return d.isFinished(); } + + /// + /// Returns whether the task is finished and the value has not been taken yet. + /// +#ifndef QXMPP_DOC + template<typename U = T, std::enable_if_t<(!std::is_void_v<U>)> * = nullptr> +#endif + [[nodiscard]] bool hasResult() const + { + return d.result() != nullptr; + } + + /// + /// The result of the operation. + /// + /// \warning This can only be used once the operation is finished. + /// +#ifdef QXMPP_DOC + [[nodiscard]] const T &result() const +#else + template<typename U = T, std::enable_if_t<(!std::is_void_v<U>)> * = nullptr> + [[nodiscard]] const U &result() const +#endif + { + Q_ASSERT(isFinished()); + Q_ASSERT(hasResult()); + return *reinterpret_cast<U *>(d.result()); + } + + /// + /// Moves the result of the operation out of the task. + /// + /// Since this returns by value, you will not be able to cast the result to derived types. Use + /// result() for read-only access or register a handler function using then(). + /// + /// \warning This can only be used once the operation is finished. + /// +#ifdef QXMPP_DOC + [[nodiscard]] T takeResult() +#else + template<typename U = T, std::enable_if_t<(!std::is_void_v<U>)> * = nullptr> + [[nodiscard]] U takeResult() +#endif + { + Q_ASSERT(isFinished()); + Q_ASSERT(hasResult()); + U result = std::move(*reinterpret_cast<U *>(d.result())); + d.resetResult(); + return result; + } + + /// + /// Converts the Task into a QFuture. Afterwards the QXmppTask object is invalid. + /// + QFuture<T> toFuture(QObject *context) + { + QFutureInterface<T> interface; + + if constexpr (std::is_same_v<T, void>) { + then(context, [interface]() mutable { + interface.reportFinished(); + }); + } else { + then(context, [interface](T &&val) mutable { + interface.reportResult(val); + interface.reportFinished(); + }); + } + + return interface.future(); + } + +private: + friend class QXmppPromise<T>; + + explicit QXmppTask(QXmpp::Private::TaskPrivate data) + : d(std::move(data)) + { + } + + QXmpp::Private::TaskPrivate d; +}; + +#endif // QXMPPTASK_H |
