QXmpp Version: 1.15.1
Loading...
Searching...
No Matches
QXmppTask.h
1// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im>
2// SPDX-FileCopyrightText: 2022 Jonah BrĂ¼chert <jbb@kaidan.im>
3//
4// SPDX-License-Identifier: LGPL-2.1-or-later
5
6#ifndef QXMPPTASK_H
7#define QXMPPTASK_H
8
9#include "QXmppGlobal.h"
10
11#include <coroutine>
12#include <memory>
13#include <optional>
14
15#include <QFuture>
16#include <QPointer>
17
18#if QXMPP_DEPRECATED_SINCE(1, 11)
19#include <functional>
20#endif
21
22namespace QXmpp::Private {
23
24template<typename T>
25struct TaskData {
26 std::conditional_t<std::is_void_v<T>, std::monostate, std::optional<T>> result;
27 std::coroutine_handle<> handle;
28 QPointer<const QObject> context;
29 bool finished = false;
30 bool cancelled = false;
31 bool hasContext = false;
32 uint8_t promiseCount = 1;
33
34 ~TaskData()
35 {
36 Q_ASSERT(promiseCount == 0);
37 }
38};
39
40template<typename T>
41struct ConstRefOrVoidHelper {
42 using Type = const T &;
43};
44template<>
45struct ConstRefOrVoidHelper<void> {
46 using Type = void;
47};
48
49template<typename T>
50using ConstRefOrVoid = ConstRefOrVoidHelper<T>::Type;
51
52template<typename Continuation, typename T>
53struct InvokeContinuationResultHelper {
54 using Type = std::invoke_result_t<Continuation, T &&>;
55};
56template<typename Continuation>
57struct InvokeContinuationResultHelper<Continuation, void> {
58 using Type = std::invoke_result_t<Continuation>;
59};
60
61template<typename Continuation, typename T>
62using InvokeContinuationResult = InvokeContinuationResultHelper<Continuation, T>::Type;
63
64} // namespace QXmpp::Private
65
66template<typename T>
67class QXmppTask;
68
79template<typename T>
81{
82 using Task = QXmppTask<T>;
83 using SharedData = QXmpp::Private::TaskData<T>;
84 using SharedDataPtr = std::shared_ptr<SharedData>;
85
86 struct InlineData {
87 Task *task = nullptr;
88 QPointer<const QObject> context;
89 std::coroutine_handle<> handle;
90 bool cancelled = false;
91 bool hasContext = false;
92 };
93
94public:
95 QXmppPromise() : data(InlineData()) { }
97 [[deprecated]]
99 {
100 p.detachData();
101 data = p.data;
102 sharedData().promiseCount += 1;
103 }
106 {
107 std::swap(data, p.data);
108 if (!shared()) {
109 if (auto *task = inlineData().task) {
110 task->setPromise(this);
111 }
112 }
113 }
115 {
116 if (shared()) {
117 sharedData().promiseCount -= 1;
118
119 // cancel coroutine if any
120 if (sharedData().promiseCount == 0) {
121 if (auto handle = sharedData().handle) {
122 sharedData().handle = nullptr;
123 handle.destroy();
124 }
125 }
126 } else {
127 if (auto *task = inlineData().task) {
128 task->setPromise(nullptr);
129 }
130 // cancel coroutine if any
131 if (inlineData().handle) {
132 inlineData().handle.destroy();
133 }
134 }
135 }
136
138 [[deprecated]]
140 {
141 if (shared()) {
142 sharedData().promiseCount -= 1;
143 }
144 p.detachData();
145 data = p.data;
146 if (shared()) {
147 sharedData().promiseCount += 1;
148 }
149 return *this;
150 }
153 {
154 std::swap(data, p.data);
155 if (!shared()) {
156 if (auto *task = inlineData().task) {
157 task->setPromise(this);
158 }
159 }
160 return *this;
161 }
162
168 {
169 if (!shared()) {
170 if (inlineData().task == nullptr) {
171 return Task { this };
172 } else {
173 detachData();
174 }
175 }
176 return Task { std::get<SharedDataPtr>(data) };
177 }
178
184 void finish()
185 requires(std::is_void_v<T>)
186 {
187 if (shared()) {
188 sharedData().finished = true;
189 } else {
190 if (auto *task = inlineData().task) {
191 task->inlineData().finished = true;
192 } else {
193 // finish called without generating task
194 detachData();
195 sharedData().finished = true;
196 }
197 }
198 invokeHandle();
199 }
200
206 template<typename U>
207 void finish(U &&value)
208 requires(!std::is_void_v<T>)
209 {
210 if (shared()) {
211 sharedData().finished = true;
212 sharedData().result.emplace(std::forward<U>(value));
213 } else {
214 if (auto *task = inlineData().task) {
215 inlineData().task->inlineData().finished = true;
216 inlineData().task->inlineData().result.emplace(std::forward<U>(value));
217 } else {
218 // finish called without generating task
219 detachData();
220 sharedData().finished = true;
221 sharedData().result.emplace(std::forward<U>(value));
222 }
223 }
224 invokeHandle();
225 }
226
234 bool cancelled() const
235 {
236 return shared() ? sharedData().cancelled : inlineData().cancelled;
237 }
238
239private:
240 friend class QXmppTask<T>;
241
242 bool shared() const { return std::holds_alternative<SharedDataPtr>(data); }
243 InlineData &inlineData()
244 {
245 Q_ASSERT(!shared());
246 return std::get<InlineData>(data);
247 }
248 const InlineData &inlineData() const
249 {
250 Q_ASSERT(!shared());
251 return std::get<InlineData>(data);
252 }
253 SharedData &sharedData()
254 {
255 Q_ASSERT(shared());
256 return *std::get<SharedDataPtr>(data);
257 }
258 const SharedData &sharedData() const
259 {
260 Q_ASSERT(shared());
261 return *std::get<SharedDataPtr>(data);
262 }
263
264 bool contextAlive() const
265 {
266 if (shared()) {
267 return sharedData().context != nullptr || !sharedData().hasContext;
268 } else {
269 return inlineData().context != nullptr || !inlineData().hasContext;
270 }
271 }
272
273 void invokeHandle()
274 {
275 auto &handleRef = shared() ? sharedData().handle : inlineData().handle;
276 if (auto handle = handleRef) {
277 handleRef = nullptr;
278 if (contextAlive()) {
279 handle.resume();
280 } else {
281 handle.destroy();
282 }
283 }
284 }
285
286 // Moves data from QXmppTask object to shared_ptr that can be accessed by multiple tasks and
287 // multiple promises.
288 // Historically required because task and promise must be copyable.
289 void detachData() const
290 {
291 if (shared()) {
292 return;
293 }
294
295 if (inlineData().task != nullptr) {
296 auto &taskData = inlineData().task->inlineData();
297
298 auto sharedData = std::make_shared<SharedData>(
299 std::move(taskData.result),
300 inlineData().handle,
301 inlineData().context,
302 taskData.finished,
303 inlineData().cancelled,
304 inlineData().hasContext,
305 1);
306 inlineData().task->data = sharedData;
307 data = std::move(sharedData);
308 } else {
309 data = std::make_shared<SharedData>();
310 }
311 }
312
313 mutable std::variant<InlineData, SharedDataPtr> data;
314};
315
328template<typename T>
330{
331 using Task = QXmppTask<T>;
332 using SharedData = QXmpp::Private::TaskData<T>;
333 using SharedDataPtr = std::shared_ptr<SharedData>;
334
335 struct InlineData {
336 QXmppPromise<T> *promise = nullptr;
337 std::conditional_t<std::is_void_v<T>, std::monostate, std::optional<T>> result;
338 bool finished = false;
339 };
340
341public:
343 QXmppTask(QXmppTask &&t) : data(InlineData {})
344 {
345 std::swap(data, t.data);
346 if (!shared()) {
347 if (auto *p = inlineData().promise) {
348 p->inlineData().task = this;
349 }
350 }
351 }
352 QXmppTask(const QXmppTask &) = delete;
353 ~QXmppTask()
354 {
355 if (!shared()) {
356 if (auto *p = inlineData().promise) {
357 p->inlineData().task = nullptr;
358 }
359 }
360 }
361
364 {
365 // unregister with old promise
366 if (!shared()) {
367 if (auto *p = inlineData().promise) {
368 p->inlineData().task = nullptr;
369 // to swap nullptr into `t` in next step
370 inlineData().promise = nullptr;
371 }
372 }
373 std::swap(data, t.data);
374 // register with new promise
375 if (!shared()) {
376 if (auto *p = inlineData().promise) {
377 p->inlineData().task = this;
378 }
379 }
380 return *this;
381 }
382 QXmppTask &operator=(const QXmppTask &) = delete;
383
385 bool await_ready() const noexcept { return isFinished(); }
386 void await_suspend(std::coroutine_handle<> handle)
387 {
388 auto replace = [](auto &var, auto newValue) {
389 if (var) {
390 var.destroy();
391 }
392 var = newValue;
393 };
394
395 if (shared()) {
396 if (sharedData().promiseCount > 0 && !sharedData().cancelled) {
397 replace(sharedData().handle, handle);
398 } else {
399 handle.destroy();
400 }
401 } else {
402 if (auto *p = inlineData().promise; p && !p->cancelled()) {
403 replace(p->inlineData().handle, handle);
404 } else {
405 handle.destroy();
406 }
407 }
408 }
409 auto await_resume()
410 {
411 if constexpr (!std::is_void_v<T>) {
412 return takeResult();
413 }
414 }
416
452 template<typename Continuation>
453 auto then(const QObject *context, Continuation continuation)
455 {
456 using Result = QXmpp::Private::InvokeContinuationResult<Continuation, T>;
457 QXmppTask<T> task = std::move(*this);
458 if constexpr (std::is_void_v<T>) {
459 co_await task.withContext(context);
460 if constexpr (std::is_void_v<Result>) {
461 continuation();
462 } else {
463 co_return continuation();
464 }
465 } else {
466 if constexpr (std::is_void_v<Result>) {
467 continuation(co_await task.withContext(context));
468 } else {
469 co_return continuation(co_await task.withContext(context));
470 }
471 }
472 }
473
485 QXmppTask<T> &withContext(const QObject *c)
486 {
487 if (shared()) {
488 if (!sharedData().finished) {
489 sharedData().context = c;
490 sharedData().hasContext = true;
491 }
492 } else {
493 if (auto *p = inlineData().promise) {
494 p->inlineData().context = c;
495 p->inlineData().hasContext = true;
496 }
497 }
498 return *this;
499 }
500
509 void cancel()
510 {
511 if (shared()) {
512 sharedData().cancelled = true;
513 if (auto handle = sharedData().handle) {
514 sharedData().handle = nullptr;
515 handle.destroy();
516 }
517 } else {
518 if (auto *p = inlineData().promise) {
519 p->inlineData().cancelled = true;
520 if (auto handle = p->inlineData().handle) {
521 p->inlineData().handle = nullptr;
522 handle.destroy();
523 }
524 }
525 }
526 }
527
534 [[nodiscard]]
535 bool isFinished() const
536 {
537 return shared() ? sharedData().finished : inlineData().finished;
538 }
539
543 [[nodiscard]]
544 bool hasResult() const
545 requires(!std::is_void_v<T>)
546 {
547 return shared() ? sharedData().result.has_value() : inlineData().result.has_value();
548 }
549
555 [[nodiscard]]
556 QXmpp::Private::ConstRefOrVoid<T> result() const
557 requires(!std::is_void_v<T>)
558 {
559 Q_ASSERT(isFinished());
560 Q_ASSERT(hasResult());
561 return shared() ? sharedData().result.value() : inlineData().result.value();
562 }
563
569 [[nodiscard]]
571 requires(!std::is_void_v<T>)
572 {
573 Q_ASSERT(isFinished());
574 Q_ASSERT(hasResult());
575 auto &result = shared() ? sharedData().result : inlineData().result;
576
577 auto value = std::move(*result);
578 result.reset();
579 return value;
580 }
581
585 [[nodiscard]]
586 QFuture<T> toFuture(const QObject *context)
587 {
588 QFutureInterface<T> interface;
589
590 if constexpr (std::is_same_v<T, void>) {
591 then(context, [interface]() mutable {
592 interface.reportFinished();
593 });
594 } else {
595 then(context, [interface](T &&val) mutable {
596 interface.reportResult(val);
597 interface.reportFinished();
598 });
599 }
600
601 return interface.future();
602 }
603
604private:
605 friend class QXmppPromise<T>;
606
607 explicit QXmppTask(QXmppPromise<T> *p) : data(InlineData {})
608 {
609 inlineData().promise = p;
610
611 Q_ASSERT(p->inlineData().task == nullptr);
612 p->inlineData().task = this;
613 }
614 explicit QXmppTask(SharedDataPtr data) : data(std::move(data)) { }
615
616 bool shared() const { return std::holds_alternative<SharedDataPtr>(data); }
617 InlineData &inlineData()
618 {
619 Q_ASSERT(!shared());
620 return std::get<InlineData>(data);
621 }
622 const InlineData &inlineData() const
623 {
624 Q_ASSERT(!shared());
625 return std::get<InlineData>(data);
626 }
627 SharedData &sharedData()
628 {
629 Q_ASSERT(shared());
630 return *std::get<SharedDataPtr>(data);
631 }
632 const SharedData &sharedData() const
633 {
634 Q_ASSERT(shared());
635 return *std::get<SharedDataPtr>(data);
636 }
637
638 void setPromise(QXmppPromise<T> *p)
639 {
640 inlineData().promise = p;
641 }
642
643 std::variant<InlineData, SharedDataPtr> data;
644};
645
646namespace std {
647
648template<typename T, typename... Args>
649struct coroutine_traits<QXmppTask<T>, Args...> {
650 struct promise_type {
652
653 QXmppTask<T> get_return_object() { return p.task(); }
654 std::suspend_never initial_suspend() noexcept { return {}; }
655 std::suspend_never final_suspend() noexcept { return {}; }
656
657 void unhandled_exception()
658 {
659 // exception handling currently not supported
660 throw std::current_exception();
661 }
662
663 void return_value(T value) { p.finish(std::move(value)); }
664 };
665};
666
667template<typename... Args>
668struct coroutine_traits<QXmppTask<void>, Args...> {
669 struct promise_type {
671
672 QXmppTask<void> get_return_object() { return p.task(); }
673 std::suspend_never initial_suspend() noexcept { return {}; }
674 std::suspend_never final_suspend() noexcept { return {}; }
675
676 void unhandled_exception()
677 {
678 // exception handling currently not supported
679 throw std::current_exception();
680 }
681
682 void return_void() { p.finish(); }
683 };
684};
685
686} // namespace std
687
688namespace QXmpp {
689
690namespace Private {
691
692template<typename T>
693struct IsTaskHelper {
694 constexpr static bool Value = false;
695};
696template<typename T>
697struct IsTaskHelper<QXmppTask<T>> {
698 using Type = T;
699 constexpr static bool Value = true;
700};
701
702} // namespace Private
703
709template<typename T>
710concept IsTask = Private::IsTaskHelper<T>::Value;
711
717template<IsTask T>
718using TaskValueType = typename Private::IsTaskHelper<T>::Type;
719
720} // namespace QXmpp
721
722#endif // QXMPPTASK_H
Create and update QXmppTask objects to communicate results of asynchronous operations.
Definition QXmppTask.h:81
QXmppTask< T > task()
Definition QXmppTask.h:167
QXmppPromise< T > & operator=(const QXmppPromise< T > &p)
Definition QXmppTask.h:139
bool cancelled() const
Definition QXmppTask.h:234
QXmppPromise< T > & operator=(QXmppPromise< T > &&p)
Move assignment operator.
Definition QXmppTask.h:152
void finish(U &&value)
Definition QXmppTask.h:207
void finish()
Definition QXmppTask.h:184
QXmppPromise(const QXmppPromise< T > &p)
Definition QXmppTask.h:98
QXmppPromise(QXmppPromise< T > &&p)
Move constructor.
Definition QXmppTask.h:105
Definition QXmppTask.h:330
auto then(const QObject *context, Continuation continuation) -> QXmppTask< QXmpp::Private::InvokeContinuationResult< Continuation, T > >
Definition QXmppTask.h:453
QXmppTask(QXmppTask &&t)
Move constructor.
Definition QXmppTask.h:343
QXmppTask & operator=(QXmppTask &&t) noexcept
Move assignment operator.
Definition QXmppTask.h:363
T takeResult()
Definition QXmppTask.h:570
bool hasResult() const
Definition QXmppTask.h:544
QXmppTask< T > & withContext(const QObject *c)
Definition QXmppTask.h:485
QFuture< T > toFuture(const QObject *context)
Definition QXmppTask.h:586
QXmpp::Private::ConstRefOrVoid< T > result() const
Definition QXmppTask.h:556
void cancel()
Definition QXmppTask.h:509
bool isFinished() const
Definition QXmppTask.h:535
Definition QXmppTask.h:710
Definition Algorithms.h:14
typename Private::IsTaskHelper< T >::Type TaskValueType
Definition QXmppTask.h:718