QXmpp  Version: 1.15.1
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 
22 namespace QXmpp::Private {
23 
24 template<typename T>
25 struct 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 
40 template<typename T>
41 struct ConstRefOrVoidHelper {
42  using Type = const T &;
43 };
44 template<>
45 struct ConstRefOrVoidHelper<void> {
46  using Type = void;
47 };
48 
49 template<typename T>
50 using ConstRefOrVoid = ConstRefOrVoidHelper<T>::Type;
51 
52 template<typename Continuation, typename T>
53 struct InvokeContinuationResultHelper {
54  using Type = std::invoke_result_t<Continuation, T &&>;
55 };
56 template<typename Continuation>
57 struct InvokeContinuationResultHelper<Continuation, void> {
58  using Type = std::invoke_result_t<Continuation>;
59 };
60 
61 template<typename Continuation, typename T>
62 using InvokeContinuationResult = InvokeContinuationResultHelper<Continuation, T>::Type;
63 
64 } // namespace QXmpp::Private
65 
66 template<typename T>
67 class QXmppTask;
68 
79 template<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 
94 public:
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  }
114  ~QXmppPromise()
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 
239 private:
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 
328 template<typename T>
329 class QXmppTask
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 
341 public:
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 
604 private:
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 
646 namespace std {
647 
648 template<typename T, typename... Args>
649 struct coroutine_traits<QXmppTask<T>, Args...> {
650  struct promise_type {
651  QXmppPromise<T> p;
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 
667 template<typename... Args>
668 struct 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 
688 namespace QXmpp {
689 
690 namespace Private {
691 
692 template<typename T>
693 struct IsTaskHelper {
694  constexpr static bool Value = false;
695 };
696 template<typename T>
697 struct IsTaskHelper<QXmppTask<T>> {
698  using Type = T;
699  constexpr static bool Value = true;
700 };
701 
702 } // namespace Private
703 
709 template<typename T>
710 concept IsTask = Private::IsTaskHelper<T>::Value;
711 
717 template<IsTask T>
718 using TaskValueType = typename Private::IsTaskHelper<T>::Type;
719 
720 } // namespace QXmpp
721 
722 #endif // QXMPPTASK_H
T takeResult() requires(!std
Definition: QXmppTask.h:570
QXmppPromise(QXmppPromise< T > &&p)
Move constructor.
Definition: QXmppTask.h:105
void finish(U &&value) requires(!std
Definition: QXmppTask.h:207
QXmppPromise< T > & operator=(const QXmppPromise< T > &p)
Definition: QXmppTask.h:139
bool cancelled() const
Definition: QXmppTask.h:234
Definition: QXmppTask.h:646
QXmppTask< T > & withContext(const QObject *c)
Definition: QXmppTask.h:485
QXmppTask< T > task()
Definition: QXmppTask.h:167
void cancel()
Definition: QXmppTask.h:509
QXmppTask(QXmppTask &&t)
Move constructor.
Definition: QXmppTask.h:343
Definition: QXmppTask.h:67
void finish() requires(std
Definition: QXmppTask.h:184
bool isFinished() const
Definition: QXmppTask.h:535
QXmppTask & operator=(QXmppTask &&t) noexcept
Move assignment operator.
Definition: QXmppTask.h:363
QXmppPromise(const QXmppPromise< T > &p)
Definition: QXmppTask.h:98
bool hasResult() const requires(!std
Definition: QXmppTask.h:544
Create and update QXmppTask objects to communicate results of asynchronous operations.
Definition: QXmppTask.h:80
QXmppPromise< T > & operator=(QXmppPromise< T > &&p)
Move assignment operator.
Definition: QXmppTask.h:152
concept IsTask
Definition: QXmppTask.h:710
QXmpp::Private::ConstRefOrVoid< T > result() const requires(!std
Definition: QXmppTask.h:556
Definition: Algorithms.h:14
auto then(const QObject *context, Continuation continuation) -> QXmppTask< QXmpp::Private::InvokeContinuationResult< Continuation, T >>
Definition: QXmppTask.h:453
typename Private::IsTaskHelper< T >::Type TaskValueType
Definition: QXmppTask.h:718
QFuture< T > toFuture(const QObject *context)
Definition: QXmppTask.h:586