QXmpp Version: 1.15.0
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>
80class QXmppPromise
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]]
98 QXmppPromise(const QXmppPromise<T> &p)
99 {
100 p.detachData();
101 data = p.data;
102 sharedData().promiseCount += 1;
103 }
104
105 QXmppPromise(QXmppPromise<T> &&p)
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]]
139 QXmppPromise<T> &operator=(const QXmppPromise<T> &p)
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 }
151
152 QXmppPromise<T> &operator=(QXmppPromise<T> &&p)
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 = std::forward<U>(value);
213 } else {
214 if (auto *task = inlineData().task) {
215 inlineData().task->inlineData().finished = true;
216 inlineData().task->inlineData().result = std::forward<U>(value);
217 } else {
218 // finish called without generating task
219 detachData();
220 sharedData().finished = true;
221 sharedData().result = 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
448 template<typename Continuation>
449 auto then(const QObject *context, Continuation continuation)
451 {
452 QXmppTask<T> task = std::move(*this);
453 if constexpr (std::is_void_v<T>) {
454 co_await task.withContext(context);
455 continuation();
456 } else {
457 continuation(co_await task.withContext(context));
458 }
459 }
460
472 QXmppTask<T> &withContext(const QObject *c)
473 {
474 if (shared()) {
475 if (!sharedData().finished) {
476 sharedData().context = c;
477 sharedData().hasContext = true;
478 }
479 } else {
480 if (auto *p = inlineData().promise) {
481 p->inlineData().context = c;
482 p->inlineData().hasContext = true;
483 }
484 }
485 return *this;
486 }
487
496 void cancel()
497 {
498 if (shared()) {
499 sharedData().cancelled = true;
500 if (auto handle = sharedData().handle) {
501 sharedData().handle = nullptr;
502 handle.destroy();
503 }
504 } else {
505 if (auto *p = inlineData().promise) {
506 p->inlineData().cancelled = true;
507 if (auto handle = p->inlineData().handle) {
508 p->inlineData().handle = nullptr;
509 handle.destroy();
510 }
511 }
512 }
513 }
514
521 [[nodiscard]]
522 bool isFinished() const
523 {
524 return shared() ? sharedData().finished : inlineData().finished;
525 }
526
530 [[nodiscard]]
531 bool hasResult() const
532 requires(!std::is_void_v<T>)
533 {
534 return shared() ? sharedData().result.has_value() : inlineData().result.has_value();
535 }
536
542 [[nodiscard]]
543 QXmpp::Private::ConstRefOrVoid<T> result() const
544 requires(!std::is_void_v<T>)
545 {
546 Q_ASSERT(isFinished());
547 Q_ASSERT(hasResult());
548 return shared() ? sharedData().result.value() : inlineData().result.value();
549 }
550
556 [[nodiscard]]
558 requires(!std::is_void_v<T>)
559 {
560 Q_ASSERT(isFinished());
561 Q_ASSERT(hasResult());
562 auto &result = shared() ? sharedData().result : inlineData().result;
563
564 auto value = std::move(*result);
565 result.reset();
566 return value;
567 }
568
572 [[nodiscard]]
573 QFuture<T> toFuture(const QObject *context)
574 {
575 QFutureInterface<T> interface;
576
577 if constexpr (std::is_same_v<T, void>) {
578 then(context, [interface]() mutable {
579 interface.reportFinished();
580 });
581 } else {
582 then(context, [interface](T &&val) mutable {
583 interface.reportResult(val);
584 interface.reportFinished();
585 });
586 }
587
588 return interface.future();
589 }
590
591private:
592 friend class QXmppPromise<T>;
593
594 explicit QXmppTask(QXmppPromise<T> *p) : data(InlineData {})
595 {
596 inlineData().promise = p;
597
598 Q_ASSERT(p->inlineData().task == nullptr);
599 p->inlineData().task = this;
600 }
601 explicit QXmppTask(SharedDataPtr data) : data(std::move(data)) { }
602
603 bool shared() const { return std::holds_alternative<SharedDataPtr>(data); }
604 InlineData &inlineData()
605 {
606 Q_ASSERT(!shared());
607 return std::get<InlineData>(data);
608 }
609 const InlineData &inlineData() const
610 {
611 Q_ASSERT(!shared());
612 return std::get<InlineData>(data);
613 }
614 SharedData &sharedData()
615 {
616 Q_ASSERT(shared());
617 return *std::get<SharedDataPtr>(data);
618 }
619 const SharedData &sharedData() const
620 {
621 Q_ASSERT(shared());
622 return *std::get<SharedDataPtr>(data);
623 }
624
625 void setPromise(QXmppPromise<T> *p)
626 {
627 inlineData().promise = p;
628 }
629
630 std::variant<InlineData, SharedDataPtr> data;
631};
632
633namespace std {
634
635template<typename T, typename... Args>
636struct coroutine_traits<QXmppTask<T>, Args...> {
637 struct promise_type {
638 QXmppPromise<T> p;
639
640 QXmppTask<T> get_return_object() { return p.task(); }
641 std::suspend_never initial_suspend() noexcept { return {}; }
642 std::suspend_never final_suspend() noexcept { return {}; }
643
644 void unhandled_exception()
645 {
646 // exception handling currently not supported
647 throw std::current_exception();
648 }
649
650 void return_value(T value) { p.finish(std::move(value)); }
651 };
652};
653
654template<typename... Args>
655struct coroutine_traits<QXmppTask<void>, Args...> {
656 struct promise_type {
657 QXmppPromise<void> p;
658
659 QXmppTask<void> get_return_object() { return p.task(); }
660 std::suspend_never initial_suspend() noexcept { return {}; }
661 std::suspend_never final_suspend() noexcept { return {}; }
662
663 void unhandled_exception()
664 {
665 // exception handling currently not supported
666 throw std::current_exception();
667 }
668
669 void return_void() { p.finish(); }
670 };
671};
672
673} // namespace std
674
675namespace QXmpp {
676
677namespace Private {
678
679template<typename T>
680struct IsTaskHelper {
681 constexpr static bool Value = false;
682};
683template<typename T>
684struct IsTaskHelper<QXmppTask<T>> {
685 using Type = T;
686 constexpr static bool Value = true;
687};
688
689} // namespace Private
690
696template<typename T>
697concept IsTask = Private::IsTaskHelper<T>::Value;
698
704template<IsTask T>
705using TaskValueType = typename Private::IsTaskHelper<T>::Type;
706
707} // namespace QXmpp
708
709#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:449
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:557
bool hasResult() const
Definition QXmppTask.h:531
QXmppTask< T > & withContext(const QObject *c)
Definition QXmppTask.h:472
QFuture< T > toFuture(const QObject *context)
Definition QXmppTask.h:573
QXmpp::Private::ConstRefOrVoid< T > result() const
Definition QXmppTask.h:543
void cancel()
Definition QXmppTask.h:496
bool isFinished() const
Definition QXmppTask.h:522
Definition QXmppTask.h:697
Definition Algorithms.h:14
typename Private::IsTaskHelper< T >::Type TaskValueType
Definition QXmppTask.h:705