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 | |
| 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')
| -rw-r--r-- | src/base/QXmppFutureUtils_p.h | 56 | ||||
| -rw-r--r-- | src/base/QXmppPacket.cpp | 21 | ||||
| -rw-r--r-- | src/base/QXmppPacket_p.h | 12 | ||||
| -rw-r--r-- | src/base/QXmppPromise.h | 103 | ||||
| -rw-r--r-- | src/base/QXmppStream.cpp | 38 | ||||
| -rw-r--r-- | src/base/QXmppStream.h | 12 | ||||
| -rw-r--r-- | src/base/QXmppStreamManagement.cpp | 13 | ||||
| -rw-r--r-- | src/base/QXmppTask.cpp | 85 | ||||
| -rw-r--r-- | src/base/QXmppTask.h | 230 |
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 |
