aboutsummaryrefslogtreecommitdiff
path: root/src/base/QXmppTask.h
diff options
context:
space:
mode:
authorLinus Jahn <lnj@kaidan.im>2022-08-16 21:00:15 +0200
committerLinus Jahn <lnj@kaidan.im>2023-01-03 22:05:54 +0100
commitb17284ee7d674416e0d11f1699f73fcc606262d4 (patch)
tree86597f2bc2a1ed2d257e0cbf8e7de1ca54080c08 /src/base/QXmppTask.h
parent3271c6642439d4d3c0d8c634e2b3f4cf17b908a0 (diff)
downloadqxmpp-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.h230
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