-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathqtcoro.hpp
320 lines (258 loc) · 9.78 KB
/
qtcoro.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// classes for using coroutines with Qt signals and slots
// The goal: from within a slot, co_await on a one-shot signal
/*
Copyright (c) 2018 Jeff Trull <edaskel@att.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef QTCORO_HPP
#define QTCORO_HPP
#include <iostream>
#include <experimental/coroutine>
#include <QObject>
#include "meta.hpp"
namespace qtcoro {
//
// Special TS-required types
//
// first, the type "returned" by (more accurately, "supplied by the call expression from") our coroutines
template<typename T=void>
struct return_object {
struct promise_type;
return_object(promise_type & p)
: m_coro(std::experimental::coroutine_handle<promise_type>::from_promise(p)) {}
return_object(return_object const &) = delete;
return_object(return_object && other) {
m_coro = other.m_coro;
other.m_coro = nullptr;
}
~return_object() {
if (m_coro) {
m_coro.destroy();
}
}
// promise type must have either return_void or return_value member but not both
// not even if one is SFINAEd out - you cannot have both names present, per Lewis Baker
// So this is the fix: we specialize the base and inherit
template<typename U>
struct promise_base {
void return_value(U const&) const noexcept {
}
};
// void specialization to replace with return_void() is below at namespace scope
#ifdef INTERNAL_VOID_SPECIALIZATION
template<>
struct promise_base<void> {
void return_void() const noexcept {}
};
#endif // INTERNAL_VOID_SPECIALIZATION
struct promise_type : promise_base<T> {
// coroutine promise requirements:
auto initial_suspend() const noexcept {
return std::experimental::suspend_never(); // produce at least one value
}
auto final_suspend() const noexcept {
return std::experimental::suspend_always(); // ?? not sure
}
// either return_void or return_value will exist, depending on T
// we inherit it from promise_base
return_object get_return_object() {
return return_object(*this);
}
void unhandled_exception() {} // do nothing :)
};
private:
std::experimental::coroutine_handle<promise_type> m_coro;
};
// that specialization for void values
#ifndef INTERNAL_VOID_SPECIALIZATION
template<>
template<>
struct return_object<void>::promise_base<void> {
void return_void() const noexcept {
}
};
#endif // INTERNAL_VOID_SPECIALIZATION
//
// Slot "factory"
// Gives us something to connect() signals to, that integrates with coroutines
//
template<typename Result, typename... Args>
struct make_slot;
// for two or more arguments we supply a std::tuple from the awaiter
template<typename... Args>
struct make_slot<std::tuple<Args...>, Args...> {
using Result = std::tuple<Args...>;
auto operator()(QMetaObject::Connection& signal_conn,
Result& result,
std::experimental::coroutine_handle<>& coro_handle) {
return [&signal_conn, &coro_handle, &result]
(Args... a) {
// all our awaits are one-shot, so we immediately disconnect
QObject::disconnect(signal_conn);
// put the result where the awaiter can supply it from await_resume()
result = std::make_tuple(a...);
// resume execution inside the coroutine at the co_await
coro_handle.resume();
};
}
};
// for a single argument it's just that particular type
template<typename Arg>
struct make_slot<Arg, Arg> {
auto operator()(QMetaObject::Connection& signal_conn,
Arg& result,
std::experimental::coroutine_handle<>& coro_handle) {
return [&signal_conn, &coro_handle, &result]
(Arg a) {
QObject::disconnect(signal_conn);
result = a;
coro_handle.resume();
};
}
};
// no argument - result is void, don't set anything
template<>
struct make_slot<void>
{
auto operator()(QMetaObject::Connection& signal_conn,
std::experimental::coroutine_handle<>& coro_handle) {
return [&signal_conn, &coro_handle]() {
QObject::disconnect(signal_conn);
coro_handle.resume();
};
}
};
//
// Awaitable class
//
// first, a special base to handle possibly nullary signals
template<typename Derived, typename Result>
struct awaitable_signal_base {
// not nullary - we have a real value to set when the signal arrives
template<typename Object, typename... Args>
awaitable_signal_base(Object* src, void (Object::*method)(Args...),
std::experimental::coroutine_handle<>& coro_handle) {
signal_conn_ =
QObject::connect(src, method,
make_slot<Result, Args...>()(signal_conn_, derived()->signal_args_, coro_handle));
}
Derived * derived() { return static_cast<Derived*>(this); }
protected:
Result signal_args_;
QMetaObject::Connection signal_conn_;
};
template<typename Derived>
struct awaitable_signal_base<Derived, void> {
// nullary, i.e., no arguments to signal and nothing to supply to co_await
template<typename Object, typename... Args>
awaitable_signal_base(Object* src, void (Object::*method)(Args...),
std::experimental::coroutine_handle<>& coro_handle) {
// hook up the slot version that doesn't try to store signal args
signal_conn_ = QObject::connect(src, method,
make_slot<void>()(signal_conn_, coro_handle));
}
protected:
QMetaObject::Connection signal_conn_;
// no need to store signal arguments since there are none
};
// forward reference for our "signal args -> co_await result" TMP code
template<typename F>
struct signal_args_t;
// The rest of our awaitable
template<typename Signal, typename Result = typename signal_args_t<Signal>::type>
struct awaitable_signal : awaitable_signal_base<awaitable_signal<Signal, Result>, Result> {
using obj_t = typename member_fn_t<Signal>::cls_t;
awaitable_signal(obj_t * src, Signal method)
: awaitable_signal_base<awaitable_signal, Result>(src, method, coro_handle_) {}
struct awaiter {
awaiter(awaitable_signal * awaitable) : awaitable_(awaitable) {}
bool await_ready() const noexcept {
return false; // we are waiting for the signal to arrive
}
template<typename P>
void await_suspend(std::experimental::coroutine_handle<P> handle) noexcept {
// we have now been suspended but are able to do something before
// returning to caller-or-resumer
// such as storing the coroutine handle!
awaitable_->coro_handle_ = handle; // store for later resumption
}
template<typename R = Result>
typename std::enable_if_t<!std::is_same_v<R, void>, R>
await_resume() noexcept {
return awaitable_->signal_args_;
}
template<typename R = Result>
typename std::enable_if_t<std::is_same_v<R, void>, void>
await_resume() noexcept {}
private:
awaitable_signal* awaitable_;
};
awaiter operator co_await () { return awaiter{this}; }
private:
std::experimental::coroutine_handle<> coro_handle_;
};
template<typename T, typename F>
awaitable_signal<F>
make_awaitable_signal(T * t, F fn) {
return awaitable_signal<F>{t, fn};
}
//
// some light metaprogramming
//
// deduce the type we want to return from co_await from the signal's signature
// result of co_await should be void, one value, or a tuple of values
// depending on how many parameters the signal has
// produce void or T for small tuples
template<typename T>
struct special_case_tuple {
using type = T;
};
// just one type
template<typename T>
struct special_case_tuple<std::tuple<T>> {
using type = T;
};
// empty list
template<>
struct special_case_tuple<std::tuple<>> {
using type = void;
};
//
// Use a simple predicate and the filter metafunction
// to make a list of non-empty types
//
template<typename T>
using not_empty = std::negation<std::is_empty<T>>;
template<typename Sequence>
struct filter_empty_types {
using type = typename filter<not_empty, Sequence>::type;
};
// now put it all together:
template<typename F>
struct signal_args_t {
// get argument list
using args_t = typename member_fn_t<F>::arglist_t;
using classname_t = typename member_fn_t<F>::cls_t;
// remove any empty (including "QPrivateSignal") parameters from the list
using no_empty_t = typename filter_empty_types<args_t>::type;
// apply std::decay_t to all arg types
using decayed_args_t = typename apply_to_tuple<std::decay_t, no_empty_t>::type;
// special case 0 and 1 argument
using type = typename special_case_tuple<decayed_args_t>::type;
};
}
#endif // QTCORO_HPP