aboutsummaryrefslogtreecommitdiff
path: root/src/base
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
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')
-rw-r--r--src/base/QXmppFutureUtils_p.h56
-rw-r--r--src/base/QXmppPacket.cpp21
-rw-r--r--src/base/QXmppPacket_p.h12
-rw-r--r--src/base/QXmppPromise.h103
-rw-r--r--src/base/QXmppStream.cpp38
-rw-r--r--src/base/QXmppStream.h12
-rw-r--r--src/base/QXmppStreamManagement.cpp13
-rw-r--r--src/base/QXmppTask.cpp85
-rw-r--r--src/base/QXmppTask.h230
9 files changed, 504 insertions, 66 deletions
diff --git a/src/base/QXmppFutureUtils_p.h b/src/base/QXmppFutureUtils_p.h
index 311e7019..5e6b5ca8 100644
--- a/src/base/QXmppFutureUtils_p.h
+++ b/src/base/QXmppFutureUtils_p.h
@@ -16,6 +16,7 @@
//
#include "QXmppIq.h"
+#include "QXmppPromise.h"
#include "QXmppSendResult.h"
#include <memory>
@@ -85,6 +86,21 @@ inline QFuture<void> makeReadyFuture()
}
#endif
+template<typename T>
+QXmppTask<T> makeReadyTask(T &&value)
+{
+ QXmppPromise<T> promise;
+ promise.finish(std::move(value));
+ return promise.task();
+}
+
+inline QXmppTask<void> makeReadyTask()
+{
+ QXmppPromise<void> promise;
+ promise.finish();
+ return promise.task();
+}
+
template<typename T, typename Handler>
void awaitLast(const QFuture<T> &future, QObject *context, Handler handler)
{
@@ -123,18 +139,14 @@ void await(const QFuture<void> &future, QObject *context, Handler handler)
}
template<typename Result, typename Input, typename Converter>
-auto chain(const QFuture<Input> &source, QObject *context, Converter task) -> QFuture<Result>
+auto chain(QXmppTask<Input> &&source, QObject *context, Converter task) -> QXmppTask<Result>
{
- QFutureInterface<Result> resultInterface(QFutureInterfaceBase::Started);
+ QXmppPromise<Result> promise;
- auto *watcher = new QFutureWatcher<Input>(context);
- QObject::connect(watcher, &QFutureWatcherBase::finished, context, [=]() mutable {
- resultInterface.reportResult(task(watcher->result()));
- resultInterface.reportFinished();
- watcher->deleteLater();
+ source.then(context, [=](Input &&input) mutable {
+ promise.finish(task(std::move(input)));
});
- watcher->setFuture(source);
- return resultInterface.future();
+ return promise.task();
}
template<typename IqType, typename Input, typename Converter>
@@ -169,21 +181,21 @@ auto parseIq(Input &&sendResult) -> Result
}
template<typename Input, typename Converter>
-auto chainIq(QFuture<Input> &&input, QObject *context, Converter convert) -> QFuture<decltype(convert({}))>
+auto chainIq(QXmppTask<Input> &&input, QObject *context, Converter convert) -> QXmppTask<decltype(convert({}))>
{
using Result = decltype(convert({}));
using IqType = std::decay_t<first_argument_t<Converter>>;
- return chain<Result>(std::move(input), context, [convert { std::move(convert) }](Input &&input) -> Result {
+ return chain<Result>(std::move(input), context, [convert = std::move(convert)](Input &&input) -> Result {
return parseIq<IqType>(std::move(input), convert);
});
}
template<typename Result, typename Input>
-auto chainIq(QFuture<Input> &&input, QObject *context) -> QFuture<Result>
+auto chainIq(QXmppTask<Input> &&input, QObject *context) -> QXmppTask<Result>
{
// IQ type is first std::variant parameter
using IqType = std::decay_t<decltype(std::get<0>(Result {}))>;
- return chain<Result>(std::move(input), context, [](Input &&sendResult) {
+ return chain<Result>(std::move(input), context, [](Input &&sendResult) mutable {
return parseIq<IqType, Result>(sendResult);
});
}
@@ -210,6 +222,24 @@ auto mapSuccess(std::variant<T, Err> var, Function lambda)
std::move(var));
}
+template<typename T>
+static auto taskFromFuture(QFuture<T> &&future) -> QXmppTask<T>
+{
+ QXmppPromise<T> promise;
+ auto *watcher = new QFutureWatcher<T>();
+ QObject::connect(watcher, &QFutureWatcher<T>::finished, [promise = std::move(promise), watcher]() mutable {
+ if constexpr (std::is_void_v<T>) {
+ promise.finish();
+ } else {
+ promise.finish(watcher->result());
+ }
+ watcher->deleteLater();
+ });
+ watcher->setFuture(future);
+
+ return promise.task();
+}
+
} // namespace QXmpp::Private
#endif // QXMPPFUTUREUTILS_P_H
diff --git a/src/base/QXmppPacket.cpp b/src/base/QXmppPacket.cpp
index d61af9da..a0921973 100644
--- a/src/base/QXmppPacket.cpp
+++ b/src/base/QXmppPacket.cpp
@@ -5,7 +5,6 @@
#include "QXmppNonza.h"
#include "QXmppPacket_p.h"
-#include <QFuture>
#include <QXmlStreamWriter>
inline QByteArray serialize(const QXmppNonza &nonza)
@@ -17,17 +16,16 @@ inline QByteArray serialize(const QXmppNonza &nonza)
}
/// \cond
-QXmppPacket::QXmppPacket(const QXmppNonza &nonza, QFutureInterface<QXmpp::SendResult> interface)
+QXmppPacket::QXmppPacket(const QXmppNonza &nonza, QXmppPromise<QXmpp::SendResult> interface)
: QXmppPacket(serialize(nonza), nonza.isXmppStanza(), std::move(interface))
{
}
-QXmppPacket::QXmppPacket(const QByteArray &data, bool isXmppStanza, QFutureInterface<QXmpp::SendResult> interface)
- : m_interface(std::move(interface)),
+QXmppPacket::QXmppPacket(const QByteArray &data, bool isXmppStanza, QXmppPromise<QXmpp::SendResult> interface)
+ : m_promise(std::move(interface)),
m_data(data),
m_isXmppStanza(isXmppStanza)
{
- m_interface.reportStarted();
}
QByteArray QXmppPacket::data() const
@@ -40,18 +38,13 @@ bool QXmppPacket::isXmppStanza() const
return m_isXmppStanza;
}
-QFuture<QXmpp::SendResult> QXmppPacket::future()
+QXmppTask<QXmpp::SendResult> QXmppPacket::task()
{
- return m_interface.future();
+ return m_promise.task();
}
-void QXmppPacket::reportFinished()
+void QXmppPacket::reportFinished(QXmpp::SendResult &&result)
{
- m_interface.reportFinished();
-}
-
-void QXmppPacket::reportResult(const QXmpp::SendResult &result)
-{
- m_interface.reportResult(result);
+ m_promise.finish(std::move(result));
}
/// \endcond
diff --git a/src/base/QXmppPacket_p.h b/src/base/QXmppPacket_p.h
index a7170a17..f5d95f85 100644
--- a/src/base/QXmppPacket_p.h
+++ b/src/base/QXmppPacket_p.h
@@ -6,6 +6,7 @@
#define QXMPPPACKET_H
#include "QXmppGlobal.h"
+#include "QXmppPromise.h"
#include "QXmppSendResult.h"
#include <memory>
@@ -17,19 +18,18 @@ class QXmppNonza;
class QXmppPacket
{
public:
- QXmppPacket(const QXmppNonza &nonza, QFutureInterface<QXmpp::SendResult> = {});
- QXmppPacket(const QByteArray &data, bool isXmppStanza, QFutureInterface<QXmpp::SendResult> = {});
+ QXmppPacket(const QXmppNonza &nonza, QXmppPromise<QXmpp::SendResult> = {});
+ QXmppPacket(const QByteArray &data, bool isXmppStanza, QXmppPromise<QXmpp::SendResult> = {});
QByteArray data() const;
bool isXmppStanza() const;
- QFuture<QXmpp::SendResult> future();
+ QXmppTask<QXmpp::SendResult> task();
- void reportFinished();
- void reportResult(const QXmpp::SendResult &);
+ void reportFinished(QXmpp::SendResult &&);
private:
- QFutureInterface<QXmpp::SendResult> m_interface;
+ QXmppPromise<QXmpp::SendResult> m_promise;
QByteArray m_data;
bool m_isXmppStanza;
};
diff --git a/src/base/QXmppPromise.h b/src/base/QXmppPromise.h
new file mode 100644
index 00000000..09bc5bcb
--- /dev/null
+++ b/src/base/QXmppPromise.h
@@ -0,0 +1,103 @@
+// 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 QXMPPPROMISE_H
+#define QXMPPPROMISE_H
+
+#include "QXmppTask.h"
+
+///
+/// \brief Create and update QXmppTask objects to communicate results of asynchronous operations.
+///
+/// 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 QXmppPromise
+{
+public:
+ template<typename U = T, std::enable_if_t<std::is_void_v<U>> * = nullptr>
+ QXmppPromise()
+ : d(QXmpp::Private::TaskPrivate(nullptr))
+ {
+ }
+
+ template<typename U = T, std::enable_if_t<!std::is_void_v<U>> * = nullptr>
+ QXmppPromise()
+ : d(QXmpp::Private::TaskPrivate([](void *r) { delete static_cast<T *>(r); }))
+ {
+ }
+
+ ///
+ /// Report that the asynchronous operation has finished, and call the connected handler of the
+ /// QXmppTask<T> belonging to this promise.
+ ///
+ /// \param value The result of the asynchronous computation
+ ///
+#ifdef QXMPP_DOC
+ void reportFinished(T &&value)
+#else
+ template<typename U, std::enable_if_t<!std::is_void_v<T> && std::is_base_of_v<T, U>> * = nullptr>
+ void finish(U &&value)
+#endif
+ {
+ Q_ASSERT(!d.isFinished());
+ d.setFinished(true);
+ if (d.continuation()) {
+ if (d.isContextAlive()) {
+ d.invokeContinuation(&value);
+ }
+ } else {
+ d.setResult(new U(std::move(value)));
+ }
+ }
+
+ /// \cond
+ template<typename U, std::enable_if_t<!std::is_void_v<T> && std::is_constructible_v<T, U> && !std::is_base_of_v<T, U>> * = nullptr>
+ void finish(U &&value)
+ {
+ Q_ASSERT(!d.isFinished());
+ d.setFinished(true);
+ if (d.continuation()) {
+ if (d.isContextAlive()) {
+ T convertedValue { std::move(value) };
+ d.invokeContinuation(&convertedValue);
+ }
+ } else {
+ d.setResult(new T(std::move(value)));
+ }
+ }
+
+ template<typename U = T, std::enable_if_t<std::is_void_v<U>> * = nullptr>
+ void finish()
+ {
+ Q_ASSERT(!d.isFinished());
+ d.setFinished(true);
+ if (d.continuation()) {
+ if (d.isContextAlive()) {
+ d.invokeContinuation(nullptr);
+ }
+ }
+ }
+ /// \endcond
+
+ ///
+ /// Obtain a handle to this promise that allows to obtain the value that will be produced
+ /// asynchronously.
+ ///
+ QXmppTask<T> task()
+ {
+ return QXmppTask<T>(d);
+ }
+
+private:
+ QXmpp::Private::TaskPrivate d;
+};
+
+#endif // QXMPPPROMISE_H
diff --git a/src/base/QXmppStream.cpp b/src/base/QXmppStream.cpp
index cbe40b8a..d1a5ccd2 100644
--- a/src/base/QXmppStream.cpp
+++ b/src/base/QXmppStream.cpp
@@ -35,7 +35,7 @@ static bool randomSeeded = false;
struct IqState
{
- QFutureInterface<QXmppStream::IqResult> interface;
+ QXmppPromise<QXmppStream::IqResult> interface;
QString jid;
};
@@ -172,7 +172,7 @@ bool QXmppStream::sendPacket(const QXmppNonza &nonza)
///
/// \since QXmpp 1.5
///
-QFuture<QXmpp::SendResult> QXmppStream::send(QXmppNonza &&nonza)
+QXmppTask<QXmpp::SendResult> QXmppStream::send(QXmppNonza &&nonza)
{
bool success;
return send(QXmppPacket(nonza), success);
@@ -183,13 +183,13 @@ QFuture<QXmpp::SendResult> QXmppStream::send(QXmppNonza &&nonza)
///
/// \since QXmpp 1.5
///
-QFuture<QXmpp::SendResult> QXmppStream::send(QXmppPacket &&packet)
+QXmppTask<QXmpp::SendResult> QXmppStream::send(QXmppPacket &&packet)
{
bool success;
return send(std::move(packet), success);
}
-QFuture<QXmpp::SendResult> QXmppStream::send(QXmppPacket &&packet, bool &writtenToSocket)
+QXmppTask<QXmpp::SendResult> QXmppStream::send(QXmppPacket &&packet, bool &writtenToSocket)
{
// the writtenToSocket parameter is just for backwards compat (see
// QXmppStream::sendPacket())
@@ -198,7 +198,7 @@ QFuture<QXmpp::SendResult> QXmppStream::send(QXmppPacket &&packet, bool &written
// handle stream management
d->streamManager.handlePacketSent(packet, writtenToSocket);
- return packet.future();
+ return packet.task();
}
///
@@ -208,7 +208,7 @@ QFuture<QXmpp::SendResult> QXmppStream::send(QXmppPacket &&packet, bool &written
///
/// \since QXmpp 1.5
///
-QFuture<QXmppStream::IqResult> QXmppStream::sendIq(QXmppIq &&iq)
+QXmppTask<QXmppStream::IqResult> QXmppStream::sendIq(QXmppIq &&iq)
{
using namespace QXmpp;
@@ -233,18 +233,18 @@ QFuture<QXmppStream::IqResult> QXmppStream::sendIq(QXmppIq &&iq)
///
/// \since QXmpp 1.5
///
-QFuture<QXmppStream::IqResult> QXmppStream::sendIq(QXmppPacket &&packet, const QString &id, const QString &to)
+QXmppTask<QXmppStream::IqResult> QXmppStream::sendIq(QXmppPacket &&packet, const QString &id, const QString &to)
{
using namespace QXmpp;
if (id.isEmpty() || d->runningIqs.contains(id)) {
- return makeReadyFuture<IqResult>(QXmppError {
+ return makeReadyTask<IqResult>(QXmppError {
QStringLiteral("Invalid IQ id: empty or in use."),
SendError::Disconnected });
}
if (to.isEmpty()) {
- return makeReadyFuture<IqResult>(QXmppError {
+ return makeReadyTask<IqResult>(QXmppError {
QStringLiteral("The 'to' address must be set so the stream can match the response."),
SendError::Disconnected });
}
@@ -253,15 +253,13 @@ QFuture<QXmppStream::IqResult> QXmppStream::sendIq(QXmppPacket &&packet, const Q
if (sendFuture.isFinished()) {
if (std::holds_alternative<QXmppError>(sendFuture.result())) {
// early exit (saves QFutureWatcher)
- return makeReadyFuture<IqResult>(std::get<QXmppError>(sendFuture.result()));
+ return makeReadyTask<IqResult>(std::get<QXmppError>(sendFuture.result()));
}
} else {
- awaitLast(sendFuture, this, [this, id](SendResult result) {
+ sendFuture.then(this, [this, id](SendResult result) {
if (std::holds_alternative<QXmppError>(result)) {
if (auto itr = d->runningIqs.find(id); itr != d->runningIqs.end()) {
- itr.value().interface.reportResult(std::get<QXmppError>(result));
- itr.value().interface.reportFinished();
-
+ itr.value().interface.finish(std::get<QXmppError>(result));
d->runningIqs.erase(itr);
}
}
@@ -269,12 +267,12 @@ QFuture<QXmppStream::IqResult> QXmppStream::sendIq(QXmppPacket &&packet, const Q
}
IqState state {
- QFutureInterface<IqResult>(QFutureInterfaceBase::Started),
+ {},
to,
};
- auto future = state.interface.future();
+ auto task = state.interface.task();
d->runningIqs.insert(id, std::move(state));
- return future;
+ return task;
}
///
@@ -285,10 +283,9 @@ QFuture<QXmppStream::IqResult> QXmppStream::sendIq(QXmppPacket &&packet, const Q
void QXmppStream::cancelOngoingIqs()
{
for (auto &state : d->runningIqs) {
- state.interface.reportResult(QXmppError {
+ state.interface.finish(QXmppError {
QStringLiteral("IQ has been cancelled."),
QXmpp::SendError::Disconnected });
- state.interface.reportFinished();
}
d->runningIqs.clear();
}
@@ -494,8 +491,7 @@ bool QXmppStream::handleIqResponse(const QDomElement &stanza)
return false;
}
- itr.value().interface.reportResult(stanza);
- itr.value().interface.reportFinished();
+ itr.value().interface.finish(stanza);
d->runningIqs.erase(itr);
return true;
diff --git a/src/base/QXmppStream.h b/src/base/QXmppStream.h
index 464d576d..55dc7967 100644
--- a/src/base/QXmppStream.h
+++ b/src/base/QXmppStream.h
@@ -17,6 +17,8 @@
class QDomElement;
template<typename T>
+class QXmppTask;
+template<typename T>
class QFuture;
template<typename T>
class QFutureInterface;
@@ -41,12 +43,12 @@ public:
virtual bool isConnected() const;
bool sendPacket(const QXmppNonza &);
- QFuture<QXmpp::SendResult> send(QXmppNonza &&);
- QFuture<QXmpp::SendResult> send(QXmppPacket &&);
+ QXmppTask<QXmpp::SendResult> send(QXmppNonza &&);
+ QXmppTask<QXmpp::SendResult> send(QXmppPacket &&);
using IqResult = std::variant<QDomElement, QXmppError>;
- QFuture<IqResult> sendIq(QXmppIq &&);
- QFuture<IqResult> sendIq(QXmppPacket &&, const QString &id, const QString &to);
+ QXmppTask<IqResult> sendIq(QXmppIq &&);
+ QXmppTask<IqResult> sendIq(QXmppPacket &&, const QString &id, const QString &to);
void cancelOngoingIqs();
bool hasIqId(const QString &id) const;
@@ -97,7 +99,7 @@ private:
friend class tst_QXmppStream;
friend class TestClient;
- QFuture<QXmpp::SendResult> send(QXmppPacket &&, bool &);
+ QXmppTask<QXmpp::SendResult> send(QXmppPacket &&, bool &);
void processData(const QString &data);
bool handleIqResponse(const QDomElement &);
diff --git a/src/base/QXmppStreamManagement.cpp b/src/base/QXmppStreamManagement.cpp
index 853327f9..f3b183e1 100644
--- a/src/base/QXmppStreamManagement.cpp
+++ b/src/base/QXmppStreamManagement.cpp
@@ -353,13 +353,12 @@ void QXmppStreamManager::handlePacketSent(QXmppPacket &packet, bool sentData)
sendAcknowledgementRequest();
} else {
if (sentData) {
- packet.reportResult(QXmpp::SendSuccess { false });
+ packet.reportFinished(QXmpp::SendSuccess { false });
} else {
- packet.reportResult(QXmppError {
+ packet.reportFinished(QXmppError {
QStringLiteral("Couldn't write data to socket. No stream management enabled."),
QXmpp::SendError::SocketWriteError });
}
- packet.reportFinished();
}
}
@@ -418,8 +417,7 @@ void QXmppStreamManager::setAcknowledgedSequenceNumber(unsigned int sequenceNumb
{
for (auto it = m_unacknowledgedStanzas.begin(); it != m_unacknowledgedStanzas.end();) {
if (it.key() <= sequenceNumber) {
- it->reportResult(QXmpp::SendSuccess { true });
- it->reportFinished();
+ it->reportFinished(QXmpp::SendSuccess { true });
it = m_unacknowledgedStanzas.erase(it);
} else {
break;
@@ -472,8 +470,9 @@ void QXmppStreamManager::sendAcknowledgementRequest()
void QXmppStreamManager::resetCache()
{
for (auto &packet : m_unacknowledgedStanzas) {
- packet.reportResult(QXmppError { QStringLiteral("Disconnected"), QXmpp::SendError::Disconnected });
- packet.reportFinished();
+ packet.reportFinished(QXmppError {
+ QStringLiteral("Disconnected"),
+ QXmpp::SendError::Disconnected });
}
m_unacknowledgedStanzas.clear();
diff --git a/src/base/QXmppTask.cpp b/src/base/QXmppTask.cpp
new file mode 100644
index 00000000..c17a6e4f
--- /dev/null
+++ b/src/base/QXmppTask.cpp
@@ -0,0 +1,85 @@
+// SPDX-FileCopyrightText: 2023 Linus Jahn <lnj@kaidan.im>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppTask.h"
+
+#include <QDebug>
+
+namespace QXmpp::Private {
+
+struct TaskData
+{
+ QPointer<QObject> context;
+ std::function<void(TaskPrivate &, void *)> continuation;
+ void *result = nullptr;
+ void (*freeResult)(void *);
+ bool finished = false;
+
+ ~TaskData()
+ {
+ if (freeResult) {
+ freeResult(result);
+ }
+ }
+};
+
+} // namespace QXmpp::Private
+
+QXmpp::Private::TaskPrivate::TaskPrivate(void (*freeResult)(void *))
+ : d(std::make_shared<QXmpp::Private::TaskData>())
+{
+ d->freeResult = freeResult;
+}
+
+QXmpp::Private::TaskPrivate::~TaskPrivate()
+{
+}
+
+bool QXmpp::Private::TaskPrivate::isFinished() const
+{
+ return d->finished;
+}
+
+void QXmpp::Private::TaskPrivate::setFinished(bool finished)
+{
+ d->finished = finished;
+}
+
+bool QXmpp::Private::TaskPrivate::isContextAlive()
+{
+ return !d->context.isNull();
+}
+
+void QXmpp::Private::TaskPrivate::setContext(QObject *obj)
+{
+ d->context = obj;
+}
+
+void *QXmpp::Private::TaskPrivate::result() const
+{
+ return d->result;
+}
+
+void QXmpp::Private::TaskPrivate::setResult(void *result)
+{
+ if (d->freeResult) {
+ d->freeResult(d->result);
+ }
+ d->result = result;
+}
+
+const std::function<void(QXmpp::Private::TaskPrivate &, void *)> QXmpp::Private::TaskPrivate::continuation() const
+{
+ return d->continuation;
+}
+
+void QXmpp::Private::TaskPrivate::setContinuation(std::function<void(TaskPrivate &, void *)> &&continuation)
+{
+ d->continuation = continuation;
+}
+
+void QXmpp::Private::TaskPrivate::invokeContinuation(void *result)
+{
+ d->continuation(*this, result);
+}
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