You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: docpages/advanced_reference/coroutines.md
+11-14
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
\page coroutines Advanced commands with coroutines
2
2
3
-
\warning D++ Coroutines are a very new feature and are currently only supported by D++ on g++ 13.1 and MSVC 19.37, and the CMake option DPP_COROmust be enabled. They are experimental and may have bugs or even crashes, please report any to [GitHub Issues](https://github.com/brainboxdotcc/DPP/issues) or to our [Discord Server](https://discord.gg/dpp).
3
+
\warning D++ Coroutines are a very new feature and are currently only supported by D++ on g++ 11, clang/LLVM 14, and MSVC 19.37 or above. Additionnally, D++ must be built with the CMake option DPP_CORO, and your program must both define the macro DPP_CORO and use C++20 or above. The feature is experimental and may have bugs or even crashes, please report any to [GitHub Issues](https://github.com/brainboxdotcc/DPP/issues) or to our [Discord Server](https://discord.gg/dpp).
4
4
5
5
### What is a coroutine?
6
6
@@ -19,7 +19,7 @@ int main() {
19
19
20
20
/* Message handler to look for a command called !file */
21
21
/* Make note of passing the event by value, this is important (explained below) */
Coroutines can make commands simpler by eliminating callbacks, which can be very handy in the case of complex commands that rely on a lot of different data or steps.
48
+
Coroutines can make commands simpler by eliminating callbacks, which can be very handy in the case of complex commands that rely on a lot of different data or steps.
49
49
50
-
In order to be a coroutine, a function has to return a special type with special functions; D++ offers `dpp::task`which is designed to work seamlessly with asynchronous calls through `dpp::awaitable`, which all the functions starting with `co_` such as `dpp::cluster::co_message_create` return. To turn a function into a coroutine, simply make it return `dpp::task<void>` as seen in the example at line 10. Inside of a `dpp::task`, someone can use `co_return` in place of `return` to return a value.
50
+
In order to be a coroutine, a function has to return a special type with special functions; D++ offers `dpp::job`, `dpp::task<R>`, and `dpp::coroutine<R>`, which are designed to work seamlessly with asynchronous calls through `dpp::async`, which all the functions starting with `co_` such as `dpp::cluster::co_message_create` return. Event routers can have a `dpp::job` attached to them, as this object allows to create coroutines that can execute on their own, asynchronously. More on that and the difference between it and the other two types later. To turn a function into a coroutine, simply make it return `dpp::job` as seen in the example at line 10, then use `co_await` on awaitable types or `co_return`. The moment the execution encounters one of these two keywords, the function is transformed into a coroutine.
51
51
52
-
When an awaitable is `co_await`-ed, the request is sent, the coroutine suspends (pauses) and returns back to its caller : in other words, the program is free to go and do other things while the data is being retrieved, D++ will resume your coroutine when it has the data you need which will be returned from the `co_await` expression.
52
+
When using a co_* function such as `co_message_create`, the request is sent immediately and the returned `dpp::async` can be `co_await`-ed, at which point the coroutine suspends (pauses) and returns back to its caller : in other words, the program is free to go and do other things while the data is being retrieved and D++ will resume your coroutine when it has the data you need, which will be returned from the `co_await` expression.
53
53
54
-
Awaitable objects can be wrapped with `dpp::async` : this will send the call immediately but not suspend the coroutine, allowing to execute several requests in parallel. The async object can then be co_awaited later when it is depended on.
55
-
56
-
\attention As a rule of thumb when making dpp::task objects and in general coroutines, always prefer taking parameters by value and avoid capture : this may be confusing but a coroutine is *not* the lambda creating it, the captures are not bound to it and the code isn't ran inside the lambda. The lambda that returns a dpp::task simply returns a task object containing the code, which goes on to live on its own, separate from the lambda.
57
-
Similarly, with reference parameters, the object they reference to might be destroyed while the coroutine is suspended and resumed in another thread, which is why you want to pass by value. See also [lambdas and locals](/lambdas-and-locals.html) except this also applies to parameters in the case of coroutines.
54
+
\attention You may hear that coroutines are "writing async code as if it was sync", while this is sort of correct, it may limit your understandings and especially of the dangers of coroutines. I find **they are best thought of as a shortcut for a state machine**. If you've ever written one, you know what this means : think of the lambda as *its constructor*, in which captures are variable parameters. Think of the parameters passed to your lambda as data members in your state machine. References are kept as references, and by the time the state machine is resumed, the reference may be dangling : [this is not good](/lambdas-and-locals.html)! As a rule of thumb when making coroutines, **always prefer taking parameters by value and avoid lambda capture**.
58
55
59
56
### Several steps in one
60
57
61
58
\note The next example assumes you are already familiar with how to use [slash commands](/firstbot.html), [parameters](/slashcommands.html), and [sending files through a command](/discord-application-command-file-upload.html).
62
59
63
-
Coroutines allow to write asynchronous functions almost as if they were executed synchronously, without the need for callbacks, which can save a lot of pain with keeping track of different data. Here is another example of what is made easier with coroutines : an "addemoji" command taking a file and a name as a parameter. This means downloading the emoji, submitting it to Discord, and finally replying, with some error handling along the way.
60
+
Here is another example of what is made easier with coroutines : an "addemoji" command taking a file and a name as a parameter. This means downloading the emoji, submitting it to Discord, and finally replying, with some error handling along the way. Normally we would have to use callbacks and some sort of object keeping track of our state, but with coroutines, it becomes much simpler :
if (event.command.get_command_name() == "addemoji") {
75
72
dpp::cluster *cluster = event.from->creator;
76
73
// Retrieve parameter values
@@ -133,9 +130,9 @@ int main() {
133
130
134
131
\note This next example is fairly advanced and makes use of many of both C++ and D++'s advanced features.
135
132
136
-
Lastly, `dpp::task` takes its return type as a template parameter, which allows you to use tasks inside tasks and return a result from them.
133
+
Earlier we mentioned two other types of coroutines provided by dpp : `dpp::coroutine<R>` and `dpp::task<R>`. They both take their return type as a template parameter, which may be void. Both `dpp::job` and `dpp::task<R>` start on the constructor for asynchronous execution, however only the latter can and **must** be co_await-ed. This allows you to retrieve its return value. `dpp::coroutine<R>` also has a return value and can be co_await-ed, however it only starts when co_await-ing, meaning it is executed synchronously.
137
134
138
-
Here is an example of a command making use of that to retrieve the avatar of a specified user, or if missing, the sender :
135
+
Here is an example of a command making use of `dpp::task<R>` to retrieve the avatar of a specified user, or if missing, the sender :
Copy file name to clipboardexpand all lines: include/dpp/coro/async.h
+57-7
Original file line number
Diff line number
Diff line change
@@ -103,8 +103,9 @@ struct async_callback_data {
103
103
};
104
104
105
105
/**
106
-
* @brief Base class of dpp::async<R>. This class should not be used directly by a user, use dpp::async<R> instead.
106
+
* @brief Base class of dpp::async<R>.
107
107
*
108
+
* @warning This class should not be used directly by a user, use dpp::async<R> instead.
108
109
* @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::async<R> so a user cannot call await_suspend and await_resume directly.
* @brief Construct an empty async. Using `co_await` on an empty async is undefined behavior.
413
+
*/
414
+
async() noexcept;
415
+
416
+
/**
417
+
* @brief Destructor. If any callback is pending it will be aborted.
418
+
*/
419
+
~async();
420
+
421
+
/**
422
+
* @brief Copy constructor is disabled
423
+
*/
424
+
async(const async &);
425
+
426
+
/**
427
+
* @brief Move constructor
428
+
*
429
+
* NOTE: Despite being marked noexcept, this function uses std::lock_guard which may throw. The implementation assumes this can never happen, hence noexcept. Report it if it does, as that would be a bug.
430
+
*
431
+
* @remark Using the moved-from async after this function is undefined behavior.
432
+
* @param other The async object to move the data from.
433
+
*/
434
+
async(async &&other) noexcept = default;
435
+
436
+
/**
437
+
* @brief Copy assignment is disabled
438
+
*/
439
+
async &operator=(const async &) = delete;
440
+
441
+
/**
442
+
* @brief Move assignment operator.
443
+
*
444
+
* NOTE: Despite being marked noexcept, this function uses std::lock_guard which may throw. The implementation assumes this can never happen, hence noexcept. Report it if it does, as that would be a bug.
445
+
*
446
+
* @remark Using the moved-from async after this function is undefined behavior.
447
+
* @param other The async object to move the data from
Copy file name to clipboardexpand all lines: include/dpp/coro/coroutine.h
+55-2
Original file line number
Diff line number
Diff line change
@@ -44,8 +44,9 @@ template <typename R>
44
44
using coroutine_handle = std_coroutine::coroutine_handle<coroutine_promise<R>>;
45
45
46
46
/**
47
-
* @brief Base class of dpp::coroutine<R>. This class should not be used directly by a user, use dpp::coroutine<R> instead.
47
+
* @brief Base class of dpp::coroutine<R>.
48
48
*
49
+
* @warn This class should not be used directly by a user, use dpp::coroutine<R> instead.
49
50
* @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::coroutine<R> so a user cannot call await_suspend and await_resume directly.
* @brief Base class has friend access for CRTP downcast
185
+
* @brief Internal use only base class containing common logic between coroutine<R> and coroutine<void>. It also serves to prevent await_suspend and await_resume from being used directly.
186
+
*
187
+
* @warning For internal use only, do not use.
188
+
* @see operator co_await()
185
189
*/
186
190
friendclassdetail::coroutine_base<R>;
187
191
@@ -207,9 +211,54 @@ class coroutine : private detail::coroutine_base<R> {
207
211
}
208
212
209
213
public:
214
+
#ifdef _DOXYGEN_ // :))))
215
+
/**
216
+
* @brief Default constructor, creates an empty coroutine.
217
+
*/
218
+
coroutine() = default;
219
+
220
+
/**
221
+
* @brief Copy constructor is disabled
222
+
*/
223
+
coroutine(const coroutine &) = delete;
224
+
225
+
/**
226
+
* @brief Move constructor, grabs another coroutine's handle
227
+
*
228
+
* @param other Coroutine to move the handle from
229
+
*/
230
+
coroutine(coroutine &&other) noexcept;
231
+
232
+
/**
233
+
* @brief Destructor, destroys the handle.
234
+
*/
235
+
~coroutine();
236
+
237
+
/**
238
+
* @brief Copy assignment is disabled
239
+
*/
240
+
coroutine &operator=(const coroutine &) = delete;
241
+
242
+
/**
243
+
* @brief Move assignment, grabs another coroutine's handle
244
+
*
245
+
* @param other Coroutine to move the handle from
246
+
*/
247
+
coroutine &operator=(coroutine &&other) noexcept;
248
+
249
+
/**
250
+
* @brief First function called by the standard library when the coroutine is co_await-ed.
251
+
*
252
+
* @remark Do not call this manually, use the co_await keyword instead.
253
+
* @throws invalid_operation_exception if the coroutine is empty or finished.
254
+
* @return bool Whether the coroutine is done
255
+
*/
256
+
boolawait_ready() const;
257
+
#else
210
258
using detail::coroutine_base<R>::coroutine_base; // use coroutine_base's constructors
211
259
using detail::coroutine_base<R>::operator=; // use coroutine_base's assignment operators
212
260
using detail::coroutine_base<R>::await_ready; // expose await_ready as public
261
+
#endif
213
262
214
263
/**
215
264
* @brief Suspend the caller until the coroutine completes.
@@ -242,6 +291,7 @@ class coroutine : private detail::coroutine_base<R> {
242
291
}
243
292
};
244
293
294
+
#ifndef _DOXYGEN_ // don't generate this on doxygen because `using` doesn't work and 2 copies of coroutine_base's docs is enough
245
295
/**
246
296
* @brief Base type for a coroutine, starts on co_await.
247
297
*
@@ -293,6 +343,7 @@ class coroutine<void> : private detail::coroutine_base<void> {
0 commit comments