| D++ (DPP) C++ Discord API Bot Library

Search
Loading...
Searching...
No Matches
Introduction to coroutines

Introduced in C++20, coroutines are the solution to the impracticality of callbacks. In short, a coroutine is a function that can be paused and resumed later. They are an extremely powerful alternative to callbacks for asynchronous APIs in particular, as the function can be paused when waiting for an API response, and resumed when it is received.

Let's revisit attaching a downloaded file, but this time with a coroutine:

#include <dpp/dpp.h>
int main() {
dpp::cluster bot{"token"};
bot.on_log(dpp::utility::cout_logger());
/* The event is fired when someone issues your commands */
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
if (event.command.get_command_name() == "file") {
/* Request the image from the URL specified and co_await the response */
dpp::http_request_completion_t result = co_await event.owner->co_request("https://dpp.dev/DPP-Logo.png", dpp::m_get);
/* Create a message and attach the image on success */
dpp::message msg(event.command.channel_id, "This is my new attachment:");
if (result.status == 200) {
msg.add_file("logo.png", result.body);
}
/* Send the message, with our attachment. */
event.reply(msg);
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand{"file", "Send a message with an image attached from the internet!", bot.me.id});
}
});
bot.start(dpp::st_wait);
return 0;
}
dpp::cluster
The cluster class represents a group of shards and a command queue for sending and receiving commands...
Definition cluster.h:89
dpp::cluster::global_command_create
void global_command_create(const slashcommand &s, command_completion_event_t callback=utility::log_error())
Create a global slash command (a bot can have a maximum of 100 of these).
dpp::cluster::start
void start(start_type return_after=st_wait)
Start the cluster, connecting all its shards.
dpp::cluster::on_slashcommand
event_router_t< slashcommand_t > on_slashcommand
Called when a slash command is issued. Only dpp::ctxm_chat_input types of interaction are routed to t...
Definition cluster.h:734
dpp::cluster::on_ready
event_router_t< ready_t > on_ready
Called when a shard is connected and ready. A set of cluster::on_guild_create events will follow this...
Definition cluster.h:842
dpp::cluster::on_log
event_router_t< log_t > on_log
Called when a log message is to be written to the log. You can attach any logging system here you wis...
Definition cluster.h:692
dpp::cluster::me
dpp::user me
The details of the bot user. This is assumed to be identical across all shards in the cluster....
Definition cluster.h:255
dpp::interaction::channel_id
snowflake channel_id
Optional: the channel it was sent from.
Definition appcommand.h:1043
dpp::interaction::get_command_name
std::string get_command_name() const
Get the command name for a command interaction.
dpp::managed::id
snowflake id
Unique ID of object set by Discord. This value contains a timestamp, worker ID, internal server ID,...
Definition managed.h:39
dpp::slashcommand
Represents an application command, created by your bot either globally, or on a guild.
Definition appcommand.h:1436
dpp::task
A coroutine task. It starts immediately on construction and can be co_await-ed, making it perfect for...
Definition task.h:95
dpp::utility::cout_logger
std::function< void(const dpp::log_t &)> DPP_EXPORT cout_logger()
Get a default logger that outputs to std::cout. e.g.
dpp::run_once
auto run_once()
Run some code within an if() statement only once.
Definition once.h:41
dpp::m_get
@ m_get
GET.
Definition queues.h:189
dpp::st_wait
@ st_wait
Wait forever on a condition variable. The cluster will spawn threads for each shard and start() will ...
Definition cluster.h:72
dpp::http_request_completion_t
The result of any HTTP request. Contains the headers, vital rate limit figures, and returned request ...
Definition queues.h:111
dpp::http_request_completion_t::status
uint16_t status
HTTP status. e.g. 200 = OK, 404 = Not found, 429 = Rate limited, etc.
Definition queues.h:121
dpp::http_request_completion_t::body
std::string body
Reply body.
Definition queues.h:162
dpp::interaction_create_t::command
interaction command
command interaction
Definition dispatcher.h:789
dpp::message
Represents messages sent and received on Discord.
Definition message.h:2350
dpp::ready_t
Session ready.
Definition dispatcher.h:1072
dpp::slashcommand_t
User has issued a slash command.
Definition dispatcher.h:806

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.

In order to be a coroutine, a function has to return a special type with special functions; D++ offers dpp::job, dpp::task, and dpp::coroutine, 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::task coroutine 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::task<void> 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. Coroutines that use dpp::task<void> can be used for event handlers, they can be attached to an event router just the same way as regular event handlers.

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.

Warning
  As a rule of thumb when making coroutines, always prefer taking parameters by value and avoid lambda capture! See below for an explanation.

You may hear that coroutines are "writing async code as if it was sync", while this is sort of correct, it may limit your understanding and especially 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. When you co_await something, the state machine's function exits, the program goes back to the caller, at this point the calling function may return. References are kept as references in the state machine, which means by the time the state machine is resumed, the reference may be dangling: this is not good!

Another way to think of them is just like callbacks but keeping the current scope intact. In fact this is exactly what it is, the co_* functions call the normal API calls, with a callback that resumes the coroutine, in the callback thread. This means you cannot rely on thread_local variables and need to keep in mind concurrency issues with global states, as your coroutine will be resumed in another thread than the one it started on.

It is also worth noting that coroutines can lead to cases where errors may fall through and remain uncaptured, requiring the user to manually handle the error. These exceptions occur because callbacks cannot be used in their typical manner, which can be observed in the following issue and the sample coroutine code.

D++ Library version 9.0.13 D++ Library version 9.0.12 D++ Library version 9.0.11 D++ Library version 9.0.10 D++ Library version 9.0.9 D++ Library version 9.0.8 D++ Library version 9.0.7 D++ Library version 9.0.6 D++ Library version 9.0.5 D++ Library version 9.0.4 D++ Library version 9.0.3 D++ Library version 9.0.2 D++ Library version 9.0.1 D++ Library version 9.0.0 D++ Library version 1.0.2 D++ Library version 1.0.1 D++ Library version 1.0.0