QXmpp  Version: 1.15.1
Async.h
1 // SPDX-FileCopyrightText: 2021 Linus Jahn <lnj@kaidan.im>
2 // SPDX-FileCopyrightText: 2025 Filipe Azevedo <pasnox@gmail.com>
3 //
4 // SPDX-License-Identifier: LGPL-2.1-or-later
5 
6 #ifndef ASYNC_H
7 #define ASYNC_H
8 
9 #include "QXmppAsync_p.h"
10 #include "QXmppError.h"
11 #include "QXmppGlobal.h"
12 
13 #include "Algorithms.h"
14 
15 #include <deque>
16 #include <memory>
17 #include <variant>
18 
19 #include <QFutureWatcher>
20 
21 namespace QXmpp::Private {
22 
23 template<typename Function>
24 auto later(QObject *context, Function function)
25 {
26  QMetaObject::invokeMethod(context, std::forward<Function>(function), Qt::QueuedConnection);
27 }
28 
29 // QtFuture::makeReadyFuture() is deprecated since Qt 6.6 in favor of
30 // makeReadyValueFuture() (available since Qt 6.6). Wrap it to stay
31 // warning-free while keeping Qt 6.4 compatibility.
32 #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
33 template<typename T>
34 auto makeReadyFuture(T &&value)
35 {
36  return QtFuture::makeReadyValueFuture(std::forward<T>(value));
37 }
38 #else
39 using QtFuture::makeReadyFuture;
40 #endif
41 
42 template<typename T, typename Handler>
43 void await(const QFuture<T> &future, QObject *context, Handler handler)
44 {
45  auto *watcher = new QFutureWatcher<T>(context);
46  QObject::connect(watcher, &QFutureWatcherBase::finished,
47  context, [watcher, handler = std::move(handler)]() mutable {
48  handler(watcher->result());
49  watcher->deleteLater();
50  });
51  watcher->setFuture(future);
52 }
53 
54 template<typename Handler>
55 void await(const QFuture<void> &future, QObject *context, Handler handler)
56 {
57  auto *watcher = new QFutureWatcher<void>(context);
58  QObject::connect(watcher, &QFutureWatcherBase::finished,
59  context, [watcher, handler = std::move(handler)]() mutable {
60  handler();
61  watcher->deleteLater();
62  });
63  watcher->setFuture(future);
64 }
65 
66 template<typename T, typename Err, typename Function>
67 auto mapSuccess(const std::variant<T, Err> &var, Function lambda)
68 {
69  using MappedVariant = std::variant<std::invoke_result_t<Function, const T &>, Err>;
70  return map<MappedVariant>(lambda, var);
71 }
72 
73 template<typename T, typename Err, typename Function>
74 auto mapSuccess(std::variant<T, Err> &&var, Function lambda)
75 {
76  using MappedVariant = std::variant<std::invoke_result_t<Function, T &&>, Err>;
77  return map<MappedVariant>(lambda, std::move(var));
78 }
79 
80 template<typename T, typename Err>
81 auto mapToSuccess(std::variant<T, Err> &&var)
82 {
83  return mapSuccess(std::move(var), [](T) {
84  return Success();
85  });
86 }
87 
88 // Creates a task for multiple tasks without a return value.
89 template<typename T>
90 QXmppTask<void> joinVoidTasks(QObject *context, std::vector<QXmppTask<T>> &&tasks)
91 {
92  int taskCount = tasks.size();
93  auto finishedTaskCount = std::make_shared<int>();
94 
95  auto promise = std::make_shared<QXmppPromise<void>>();
96  auto result = promise->task();
97 
98  for (auto &task : tasks) {
99  task.then(context, [=]() mutable {
100  if (++(*finishedTaskCount) == taskCount) {
101  promise->finish();
102  }
103  });
104  }
105 
106  return result;
107 }
108 
109 template<typename Params, typename Response>
110 struct AttachableRequests {
111  struct Request {
112  Params params;
113  std::vector<QXmppPromise<Response>> promises;
114  };
115 
116  std::vector<Request> requests;
117 
119  std::optional<QXmppTask<Response>> attach(const Params &key)
120  {
121  auto itr = std::ranges::find(requests, key, &Request::params);
122  if (itr != requests.end()) {
124  auto task = p.task();
125  itr->promises.push_back(std::move(p));
126  return task;
127  }
128 
129  return std::nullopt;
130  }
131 
132  QXmppTask<Response> makeNew(Params key)
133  {
134  Q_ASSERT(!contains(requests, key, &Request::params));
135 
137  auto task = p.task();
138  requests.push_back(Request { key, { std::move(p) } });
139  return task;
140  }
141 
142  void finish(const Params &key, Response &&response)
143  {
144  auto itr = std::ranges::find(requests, key, &Request::params);
145  Q_ASSERT(itr != requests.end());
146  if (itr == requests.end()) {
147  return;
148  }
149 
150  auto promises = std::move(itr->promises);
151  requests.erase(itr);
152 
153  for (auto it = promises.begin(); it != promises.end(); ++it) {
154  // copy unless this is the last iteration (then do move)
155  it->finish(std::next(it) == promises.end() ? std::move(response) : response);
156  }
157  }
158 
159  QXmppTask<Response> produce(Params key, std::function<QXmppTask<Response>(Params)> requestFunction, QObject *context)
160  {
161  if (auto task = attach(key)) {
162  return std::move(*task);
163  }
164  auto task = makeNew(key);
165  requestFunction(key).then(context, [this, key](auto &&response) {
166  finish(key, std::move(response));
167  });
168  return task;
169  }
170 };
171 
172 template<typename T>
173 struct MultiPromise {
174  std::deque<QXmppPromise<T>> promises;
175 
176  void finish(T &&response)
177  {
178  for (auto it = promises.begin(); it != promises.end(); ++it) {
179  // copy unless this is the last iteration (then do move)
180  it->finish(std::next(it) == promises.end() ? std::move(response) : response);
181  }
182  }
183  QXmppTask<T> generateTask()
184  {
185  promises.push_back(QXmppPromise<T>());
186  return promises.back().task();
187  }
188 };
189 
190 template<>
191 struct MultiPromise<void> {
192  std::vector<QXmppPromise<void>> promises;
193 
194  void finish()
195  {
196  for (auto &p : promises) {
197  p.finish();
198  }
199  }
200  QXmppTask<void> generateTask()
201  {
202  promises.push_back(QXmppPromise<void>());
203  return promises.back().task();
204  }
205 };
206 
207 } // namespace QXmpp::Private
208 
209 #endif // ASYNC_H
QXmppTask< T > task()
Definition: QXmppTask.h:167
Definition: QXmppTask.h:67
void finish() requires(std
Definition: QXmppTask.h:184
Create and update QXmppTask objects to communicate results of asynchronous operations.
Definition: QXmppTask.h:80
Definition: Algorithms.h:14