From fd6caca591d4a8c1fa060f154e27ba7317594bb2 Mon Sep 17 00:00:00 2001 From: Jaskowicz1 <46899449+Jaskowicz1@users.noreply.github.com> Date: Sun, 20 Aug 2023 14:49:44 +0100 Subject: [PATCH 01/21] docs: Fixed some doc pages with errors. (#784) --- .../autocomplete.md | 4 +++- .../interactions_and_components/components.md | 3 ++- .../components2.md | 3 +-- .../components3.md | 5 ++--- .../context_menus.md | 21 ++++++++++--------- .../example_programs/misc/cache_messages.md | 2 +- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/docpages/example_programs/interactions_and_components/autocomplete.md b/docpages/example_programs/interactions_and_components/autocomplete.md index b5031335f1..87bee8b4fe 100644 --- a/docpages/example_programs/interactions_and_components/autocomplete.md +++ b/docpages/example_programs/interactions_and_components/autocomplete.md @@ -9,10 +9,11 @@ int main() { dpp::cluster bot("token"); - bot.on_log(dpp::utility::cout_logger()); + bot.on_log(dpp::utility::cout_logger()); bot.on_ready([&bot](const dpp::ready_t & event) { if (dpp::run_once()) { + /* Create a new global command once on ready event */ bot.global_command_create(dpp::slashcommand("blep", "Send a random adorable animal photo", bot.me.id) .add_option( @@ -28,6 +29,7 @@ int main() /* The interaction create event is fired when someone issues your commands */ bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) { + /* Check which command they ran */ if (event.command.get_command_name() == "blep") { /* Fetch a parameter value from the command parameters */ diff --git a/docpages/example_programs/interactions_and_components/components.md b/docpages/example_programs/interactions_and_components/components.md index 83a93bf0af..125b3627f6 100644 --- a/docpages/example_programs/interactions_and_components/components.md +++ b/docpages/example_programs/interactions_and_components/components.md @@ -16,8 +16,9 @@ int main() { /* The event is fired when someone issues your commands */ bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + /* Check which command they ran */ - if (event.command.get_command_name == "button") { + if (event.command.get_command_name() == "button") { /* Create a message */ dpp::message msg(event.command.channel_id, "this text has a button"); diff --git a/docpages/example_programs/interactions_and_components/components2.md b/docpages/example_programs/interactions_and_components/components2.md index e1b63a50b4..1bc4ae8599 100644 --- a/docpages/example_programs/interactions_and_components/components2.md +++ b/docpages/example_programs/interactions_and_components/components2.md @@ -5,8 +5,6 @@ This example demonstrates adding multiple buttons, receiving button clicks and s ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} #include -using json = nlohmann::json; - int main() { dpp::cluster bot("token"); @@ -15,6 +13,7 @@ int main() { /* The event is fired when someone issues your commands */ bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + /* Check which command they ran */ if (event.command.get_command_name() == "math") { diff --git a/docpages/example_programs/interactions_and_components/components3.md b/docpages/example_programs/interactions_and_components/components3.md index 0691a2d848..e368414a83 100644 --- a/docpages/example_programs/interactions_and_components/components3.md +++ b/docpages/example_programs/interactions_and_components/components3.md @@ -5,8 +5,6 @@ This example demonstrates creating a select menu, receiving select menu clicks a ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} #include -using json = nlohmann::json; - int main() { dpp::cluster bot("token"); @@ -15,8 +13,9 @@ int main() { /* The event is fired when someone issues your commands */ bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { + /* Check which command they ran */ - if (event.command.get_command_name == "select") { + if (event.command.get_command_name() == "select") { /* Create a message */ dpp::message msg(event.command.channel_id, "This text has a select menu!"); diff --git a/docpages/example_programs/interactions_and_components/context_menus.md b/docpages/example_programs/interactions_and_components/context_menus.md index 11f9f105d6..f33432e847 100644 --- a/docpages/example_programs/interactions_and_components/context_menus.md +++ b/docpages/example_programs/interactions_and_components/context_menus.md @@ -18,6 +18,17 @@ int main() bot.on_log(dpp::utility::cout_logger()); + /* Use the on_user_context_menu event to look for user context menu actions */ + bot.on_user_context_menu([&](const dpp::user_context_menu_t& event) { + + /* check if the context menu name is High Five */ + if (event.command.get_command_name() == "high five") { + dpp::user user = event.get_user(); // the user who the command has been issued on + dpp::user author = event.command.get_issuing_user(); // the user who clicked on the context menu + event.reply(author.get_mention() + " slapped " + user.get_mention()); + } + }); + bot.on_ready([&bot](const dpp::ready_t &event) { if (dpp::run_once()) { @@ -32,16 +43,6 @@ int main() } }); - /* Use the on_user_context_menu event to look for user context menu actions */ - bot.on_user_context_menu([&](const dpp::user_context_menu_t& event) { - /* check if the context menu name is High Five */ - if (event.command.get_command_name() == "high five") { - dpp::user user = event.get_user(); // the user who the command has been issued on - dpp::user author = event.command.get_issuing_user(); // the user who clicked on the context menu - event.reply(author.get_mention() + " slapped " + user.get_mention()); - } - }); - /* Start bot */ bot.start(dpp::st_wait); diff --git a/docpages/example_programs/misc/cache_messages.md b/docpages/example_programs/misc/cache_messages.md index 19dde4526a..865cedd8f5 100644 --- a/docpages/example_programs/misc/cache_messages.md +++ b/docpages/example_programs/misc/cache_messages.md @@ -36,7 +36,7 @@ int main() { /* The event is fired when someone issues your commands */ bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { /* Check which command they ran */ - if (event.command.get_command_name == "get") { + if (event.command.get_command_name() == "get") { dpp::message* find_msg = message_cache.find(std::get(event.get_parameter("message_id"))); From c415bf5cfed0ae7f3650dfadcbd5b64caa465af1 Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 20 Aug 2023 16:49:06 +0200 Subject: [PATCH 02/21] docs: added docs page about checking permissions (#777) --- .../{disdppgloss.md => 02_disdppgloss.md} | 102 +++++++++--------- .../{01_installing.md => 03_installing.md} | 0 ...building_a_bot.md => 04_building_a_bot.md} | 0 ...ple_programs.md => 05_example_programs.md} | 0 ..._reference.md => 06_advanced_reference.md} | 0 ...precated_list.md => 07_deprecated_list.md} | 4 +- docpages/example_programs/misc.md | 1 + .../music_and_audio/oggopus.md | 2 +- .../the_basics/checking-member-permissions.md | 94 ++++++++++++++++ include/dpp/appcommand.h | 11 +- include/dpp/guild.h | 4 +- 11 files changed, 161 insertions(+), 57 deletions(-) rename docpages/{disdppgloss.md => 02_disdppgloss.md} (87%) rename docpages/{01_installing.md => 03_installing.md} (100%) rename docpages/{02_building_a_bot.md => 04_building_a_bot.md} (100%) rename docpages/{03_example_programs.md => 05_example_programs.md} (100%) rename docpages/{04_advanced_reference.md => 06_advanced_reference.md} (100%) rename docpages/{05_deprecated_list.md => 07_deprecated_list.md} (91%) create mode 100644 docpages/example_programs/the_basics/checking-member-permissions.md diff --git a/docpages/disdppgloss.md b/docpages/02_disdppgloss.md similarity index 87% rename from docpages/disdppgloss.md rename to docpages/02_disdppgloss.md index 94db16c39d..70c0d33f8f 100644 --- a/docpages/disdppgloss.md +++ b/docpages/02_disdppgloss.md @@ -1,51 +1,51 @@ -## A Glossary of Common Discord Terms - -This is a list of terms that one should know if you want use D++ (or any other discord library). These terms are not D++ specific, and are commonly used throughout most of the Discord developer community. This list, with a few exceptions, ***is discord specific***, that is to say that this is not a explanation of commonly used C++ terms, it is for people who are not familiar with the terminology of the discord API and libraries themselves. - -#### Glossary - -Listed in alphabetical order, with terms in bold, here are the basics on... - -1. **Action row**: A collection of up to five **components** which is attached to a message. - -2. **Audit log**: A log of **events** that have happened in a **guild**. - -3. **Auto mod**: Discord's low-code solution to moderation. However, it is very limited in scope. - -4. **Badge**: A decoration on someone's profile showing certain things about them, such as if they have nitro, if they are a discord developer, etc. - -5. **Bot token**: A secret string of characters that is used as a "login" to your bot. If you lose it or it gets leaked you will have to get a new one from the discord developer portal, so be sure to keep it in a place that is both secure and where you won't forget it. - -6. **Button**: A **component** on a message that can be styled that sends an **event** when clicked on by a user. - -7. **Cache**: A type of storage efficient for things like messages. - -8. **Callback**: While not strictly related to discord, it is used a LOT in D++. A callback is when a function is passed to another function, sort of like how you might give someone a telephone number (you give them the means to do some sort of interaction rather than asking them how to interact), which is used to handle responses to **events**. - -9. **Cluster**: A singular bot application, which is composed of one or more **shards**, a **cluster** is the center of bot development. - -10. **\(Slash\) command**: The primary way a user interacts with a bot. It is a command sent to the bot with **parameters** (which may be optional) and is initiated by staring a message with `/`. - -11. **Component**: A component is anything that can appear in a bot's message besides text, such as **buttons** and **select menus**. - -12. **Drop down/Select menu**: A **component** of a message that upon being clicked drops down and allows the user to select an option. - -13. **Embeds**: A widget attached to a message which can contain multiple fields of texts, an image, and much more information. - -14. **Ephemeral**: A message only visible to the user being replied to. - -15. **Event**: Something that a Discord bot can respond to, such as a message being sent, a **button** being clicked, or an option being selected, among others. - -16. **Guild**: What the Discord API (and most libraries for it) call a server. - -17. **Intents**: The right for a bot to receive certain data from the Discord API. - -18. **Interaction**: A object that contains information about whenever a user interacts with a application, such as sending a message or clicking a button. It is the main part of an **event** that will be accessed in an application. - -19. **Modal**: A pop up form that contains text that can be sent by a bot. - -20. **[Shards](\ref clusters-shards-guilds)**: A shard manages part of the workload of your Discord application - -21. **Snowflake**: An unsigned 64 bit integer (it can represent anything from 0 to 2^64-1) that is used by discord to identify basically everything, including but not limited to, **guilds**, users, messages, and much more. - -22. **Subcommands**: A command which is derived from a different command, such as a bot that allows a person to get statistics for discord might have a `stats guild` command and a `stats global` command, both of which are **subcommands** of `stats`. +## A Glossary of Common Discord Terms + +This is a list of terms that you should know if you want to use D++ (or any other discord library). These terms are not D++ specific, and are commonly used throughout most of the Discord developer community. This list, with a few exceptions, ***is discord specific***, that is to say that this is not a explanation of commonly used C++ terms, it is for people who are not familiar with the terminology of the discord API and libraries themselves. + +#### Glossary + +Listed in alphabetical order, with terms in bold, here are the basics on... + +1. **Action row**: A collection of up to five **components** which is attached to a message. + +2. **Audit log**: A log of **events** that have happened in a **guild**. + +3. **Auto mod**: Discord's low-code solution to moderation. However, it is very limited in scope. + +4. **Badge**: A decoration on someone's profile showing certain things about them, such as if they have nitro, if they are a discord developer, etc. + +5. **Bot token**: A secret string of characters that is used as a "login" to your bot. If you lose it or it gets leaked you will have to get a new one from the discord developer portal, so be sure to keep it in a place that is both secure and where you won't forget it. + +6. **Button**: A **component** on a message that can be styled that sends an **event** when clicked on by a user. + +7. **Cache**: A type of storage efficient for things like messages. + +8. **Callback**: While not strictly related to discord, it is used a LOT in D++. A callback is when a function is passed to another function, sort of like how you might give someone a telephone number (you give them the means to do some sort of interaction rather than asking them how to interact), which is used to handle responses to **events**. + +9. **Cluster**: A singular bot application, which is composed of one or more **shards**, a **cluster** is the center of bot development. + +10. **\(Slash\) command**: The primary way a user interacts with a bot. It is a command sent to the bot with **parameters** (which may be optional) and is initiated by staring a message with `/`. + +11. **Component**: A component is anything that can appear in a bot's message besides text, such as **buttons** and **select menus**. + +12. **Drop down/Select menu**: A **component** of a message that upon being clicked drops down and allows the user to select an option. + +13. **Embeds**: A widget attached to a message which can contain multiple fields of texts, an image, and much more information. + +14. **Ephemeral**: A message only visible to the user being replied to. + +15. **Event**: Something that a Discord bot can respond to, such as a message being sent, a **button** being clicked, or an option being selected, among others. + +16. **Guild**: What the Discord API (and most libraries for it) call a server. + +17. **Intents**: The right for a bot to receive certain data from the Discord API. + +18. **Interaction**: A object that contains information about whenever a user interacts with a application, such as sending a message or clicking a button. It is the main part of an **event** that will be accessed in an application. + +19. **Modal**: A pop up form that contains text that can be sent by a bot. + +20. **[Shards](\ref clusters-shards-guilds)**: A shard manages part of the workload of your Discord application + +21. **Snowflake**: An unsigned 64 bit integer (it can represent anything from 0 to 2^64-1) that is used by discord to identify basically everything, including but not limited to, **guilds**, users, messages, and much more. + +22. **Subcommands**: A command which is derived from a different command, such as a bot that allows a person to get statistics for discord might have a `stats guild` command and a `stats global` command, both of which are **subcommands** of `stats`. diff --git a/docpages/01_installing.md b/docpages/03_installing.md similarity index 100% rename from docpages/01_installing.md rename to docpages/03_installing.md diff --git a/docpages/02_building_a_bot.md b/docpages/04_building_a_bot.md similarity index 100% rename from docpages/02_building_a_bot.md rename to docpages/04_building_a_bot.md diff --git a/docpages/03_example_programs.md b/docpages/05_example_programs.md similarity index 100% rename from docpages/03_example_programs.md rename to docpages/05_example_programs.md diff --git a/docpages/04_advanced_reference.md b/docpages/06_advanced_reference.md similarity index 100% rename from docpages/04_advanced_reference.md rename to docpages/06_advanced_reference.md diff --git a/docpages/05_deprecated_list.md b/docpages/07_deprecated_list.md similarity index 91% rename from docpages/05_deprecated_list.md rename to docpages/07_deprecated_list.md index 0beefb210d..594b53edef 100644 --- a/docpages/05_deprecated_list.md +++ b/docpages/07_deprecated_list.md @@ -4,4 +4,6 @@ We keep things marked as depreciated until next major API version. If discord removes the function, we may remove the method from the library or replace it with a thrown exception depending on the type of function and at our discretion. -Such functions which are made to throw will then be removed at the next major API version. \ No newline at end of file +Such functions which are made to throw will then be removed at the next major API version. + +
\ No newline at end of file diff --git a/docpages/example_programs/misc.md b/docpages/example_programs/misc.md index 1d7afcdfcb..b299a0595b 100644 --- a/docpages/example_programs/misc.md +++ b/docpages/example_programs/misc.md @@ -7,3 +7,4 @@ This section lists examples that do not fit neatly into any of the categories ab * \subpage caching-messages "Caching messages" * \subpage collecting-reactions "Collecting Reactions" * \subpage cpp-eval-command-discord "Making an eval command in C++" +* \subpage checking-member-permissions "Checking permissions" diff --git a/docpages/example_programs/music_and_audio/oggopus.md b/docpages/example_programs/music_and_audio/oggopus.md index 50be5b28a4..def7807bbf 100644 --- a/docpages/example_programs/music_and_audio/oggopus.md +++ b/docpages/example_programs/music_and_audio/oggopus.md @@ -188,7 +188,7 @@ You can compile this example using the following command c++ /path/to/source.cc -ldpp -lopus -lopusfile -logg -I/usr/include/opus -## Using `liboggz` +## Using liboggz You can use `liboggz` to stream an Ogg Opus file to discord voice channel. `liboggz` provides higher level abstraction and useful APIs. Some API `liboggz` provide includes seeking and timestamp interpretation. diff --git a/docpages/example_programs/the_basics/checking-member-permissions.md b/docpages/example_programs/the_basics/checking-member-permissions.md new file mode 100644 index 0000000000..02760b537f --- /dev/null +++ b/docpages/example_programs/the_basics/checking-member-permissions.md @@ -0,0 +1,94 @@ +\page checking-member-permissions Checking permissions + +Of course most people do just iterate over the roles of a member to check for a permission. +But there's a helper method for that: dpp::guild::base_permissions gets a member's permission taking into account the server owner and role permissions. + +For total member permissions including channel overwrites use either the dpp::channel::get_user_permissions or dpp::guild::permission_overwrites method. Both do the same under the hood. + +They all return a dpp::permission class, which is a wrapper around a permission bitmask containing bits of the dpp::permissions enum. + +Demonstration: + +```cpp +dpp::channel* c = dpp::find_channel(some_channel_id); +if (c && c->get_user_permissions(member).can(dpp::p_send_messages)) { + //... +} +``` + +## Permissions in Interaction events + +### Default Command Permissions + +Discord's intended way to manage permissions for commands is through default member permissions. +You set them using dpp::slashcommand::set_default_permissions when creating or updating a command to set the default permissions a user must have to use it. +However, Server-Admins can then overwrite these permissions by their own restrictions. + +The corresponding code to create a command with default permissions would look something like this: + +```cpp +dpp::slashcommand command("ban", "Ban a member", bot.me.id); + +command.set_default_permissions(dpp::p_ban_members); // set permissions that are required by default here + +command.add_option(dpp::command_option(dpp::co_user, "user", "The user to ban", true)); +command.add_option(dpp::command_option(dpp::co_string, "reason", "The reason for banning", true)); + +bot.global_command_create(command); +``` + +### Checking permissions on your own + +If you want to check permissions on your own, the easiest way to check if a member has certain permissions in interaction events is by using the dpp::interaction::get_resolved_permission function. +The resolved list contains associated structures for the command and does not use the cache or require any extra API calls. +Note that the permissions in the resolved set are pre-calculated by discord and taking into account channel overwrites, roles and admin privileges. +So no need to loop through roles or stuff like that. + +Let's imagine the following scenario: + +You have a ban command and want to make sure the issuer has the ban permission. + +```cpp +bot.on_interaction_create([](const dpp::interaction_create_t& event) { + dpp::permission perms = event.command.get_resolved_permission(event.command.usr.id); + if (! perms.can(dpp::p_ban_members)) { + event.reply("You don't have the required permissions to ban someone!"); + return; + } +}); +``` + +\note When using default permissions you don't necessarily need to check the issuing user for any permissions in the interaction event as Discord handles all that for you. But if you'd sleep better... + +### From Parameters + +The resolved set also contains the permissions of members from command parameters. + +For example let's say you want to prohibit people from banning server admins with your ban command. + +Get the user ID from the parameters and pass it to the `get_resolved_permission` method: + +```cpp +bot.on_interaction_create([](const dpp::interaction_create_t& event) { + dpp::snowflake user_id = std::get(event.get_parameter("user")); + dpp::permission perms = event.command.get_resolved_permission(user_id); + if (perms.has(dpp::p_administrator)) { + event.reply("You can't ban Admins!"); + return; + } +}); +``` + +### The Bot's permissions + +You also might want to check if the bot itself has the ban permission before processing the command further. +You can access the bot's permissions in the dpp::interaction::app_permissions field. + +```cpp +bot.on_interaction_create([](const dpp::interaction_create_t& event) { + if (! event.command.app_permissions.can(dpp::p_ban_members)) { + event.reply("The bot doesn't have the required permission to ban anyone!"); + return; + } +}); +``` \ No newline at end of file diff --git a/include/dpp/appcommand.h b/include/dpp/appcommand.h index 28f52a7395..c380cfee95 100644 --- a/include/dpp/appcommand.h +++ b/include/dpp/appcommand.h @@ -486,30 +486,37 @@ struct DPP_EXPORT interaction_modal_response : public interaction_response, publ struct DPP_EXPORT command_resolved { /** * @brief Resolved users + * @see interaction::get_resolved_user */ std::map users; /** * @brief Resolved guild members + * @see interaction::get_resolved_member */ std::map members; /** - * @brief Resolved total guild member permissions in the channel, including overwrites + * @brief Resolved total guild member permissions including channel overwrites, permissions from roles and administrator privileges + * @see interaction::get_resolved_permission */ std::map member_permissions; /** * @brief Resolved roles + * @see interaction::get_resolved_role */ std::map roles; /** * @brief Resolved channels + * @see interaction::get_resolved_channel */ std::map channels; /** * @brief Resolved messages + * @see interaction::get_resolved_message */ std::map messages; /** * @brief Resolved attachments + * @see interaction::get_resolved_attachment */ std::map attachments; }; @@ -797,7 +804,7 @@ class DPP_EXPORT interaction : public managed, public json_interface { * - Guild roles including \@everyone * * @param user User to get permissions for - * @return permission permissions bitmask + * @return permission permissions bitmask. If the member has administrator privileges, the bitmask returns with all flags set * @note Requires role cache to be enabled (it's enabled by default). * * @warning The method will search for the guild member in the cache by the users id. @@ -772,7 +772,7 @@ class DPP_EXPORT guild : public managed, public json_interface { * - Guild roles including \@everyone * * @param member member to get permissions for - * @return permission permissions bitmask + * @return permission permissions bitmask. If the member has administrator privileges, the bitmask returns with all flags set * @note Requires role cache to be enabled (it's enabled by default). */ permission base_permissions(const guild_member &member) const; From 7b42b15a2191a817d918379aee7153c9cd34d1c7 Mon Sep 17 00:00:00 2001 From: RealTimeChris <40668522+RealTimeChris@users.noreply.github.com> Date: Mon, 21 Aug 2023 03:42:41 -0400 Subject: [PATCH 03/21] Updating to the correct AVX2 instruction. Accidentally set this to the wrong intrinsic - which would have only identified that it was AVX1 enabled instead of AVX2. --- cmake/DetectArchitecture.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/DetectArchitecture.cmake b/cmake/DetectArchitecture.cmake index e35055d8bd..10bca9cbe3 100644 --- a/cmake/DetectArchitecture.cmake +++ b/cmake/DetectArchitecture.cmake @@ -27,13 +27,13 @@ endfunction() if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(INSTRUCTION_SETS "T_AVX?/arch:AVX?auto result = _mm_testz_ps(__m128{}, __m128{})" - "T_AVX2?/arch:AVX2?auto result = _mm256_extract_epi64(__m256i{}, 0)" + "T_AVX2?/arch:AVX2?auto result = _mm256_add_epi32(__m256i{}, __m256i{})" "T_AVX512?/arch:AVX512?auto result = _mm512_add_ps(__m512i{}, __m512i{}).auto result2 = _mm512_cmplt_epu8_mask(__m512i{}, __m512i{})" ) else() set(INSTRUCTION_SETS "T_AVX?-mavx.-mpclmul.-mbmi?auto result = _mm_testz_ps(__m128{}, __m128{})" - "T_AVX2?-mavx2.-mavx.-mpclmul.-mbmi?auto result = _mm256_extract_epi64(__m256i{}, 0)" + "T_AVX2?-mavx2.-mavx.-mpclmul.-mbmi?auto result = _mm256_add_epi32(__m256i{}, __m256i{})" "T_AVX512?-mavx512bw.-mavx512f.-mavx2.-mavx.-mpclmul.-mbmi?auto result = _mm512_add_ps(__m512i{}, __m512i{}).auto result2 = _mm512_cmplt_epu8_mask(__m512i{}, __m512i{})" ) endif() From 570378a48d3c151773b232ce21d57e2b5e893174 Mon Sep 17 00:00:00 2001 From: Brain Date: Mon, 21 Aug 2023 10:05:17 +0100 Subject: [PATCH 04/21] ci: create .clabot for CLA agreements for new contribs (#787) --- .clabot | 3 +++ .github/pull_request_template.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .clabot diff --git a/.clabot b/.clabot new file mode 100644 index 0000000000..691f4af56b --- /dev/null +++ b/.clabot @@ -0,0 +1,3 @@ +{ + "contributors": ["braindigitalis", "Eremiell", "RealTimeChris", " Mishura4", "Commandserver", "Axyte", "webtax-gh", "janaSunrise"] +} diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 01fee46d2f..995c7f4135 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,4 +5,4 @@ - [ ] I have ensured that I did not break any existing API calls. - [ ] My code follows the [coding style guide](https://dpp.dev/coding-standards.html) (if you are not sure, match the code style of existing files including indent style etc). - [ ] I have not built my pull request using AI, a static analysis tool or similar without any human oversight. Where I have generated this pull request using a tool, I have justified why this is needed. -- [ ] I agree to the terms of the [DCO (Developer Certificate of Origin)]((https://dpp.dev/coding-standards.html)) + From ed0e3e2b10751c83ba24ff28f5ddbfb27e1ea86f Mon Sep 17 00:00:00 2001 From: Brain Date: Mon, 21 Aug 2023 12:00:15 +0100 Subject: [PATCH 05/21] ci: create .onefuzz to make scorecard happy (#790) --- .onefuzz | 1 + 1 file changed, 1 insertion(+) create mode 100644 .onefuzz diff --git a/.onefuzz b/.onefuzz new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/.onefuzz @@ -0,0 +1 @@ + From 7e4000f633a581b62811a50e4a85466d88741ae9 Mon Sep 17 00:00:00 2001 From: Brain Date: Mon, 21 Aug 2023 16:11:30 +0100 Subject: [PATCH 06/21] ci: delete .clabot, not required for new cla/dco bot (#789) --- .clabot | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .clabot diff --git a/.clabot b/.clabot deleted file mode 100644 index 691f4af56b..0000000000 --- a/.clabot +++ /dev/null @@ -1,3 +0,0 @@ -{ - "contributors": ["braindigitalis", "Eremiell", "RealTimeChris", " Mishura4", "Commandserver", "Axyte", "webtax-gh", "janaSunrise"] -} From 729fcede7481790493652b7c0e453baf4c929561 Mon Sep 17 00:00:00 2001 From: Amber Ehrlich Date: Tue, 8 Aug 2023 16:15:44 -0400 Subject: [PATCH 07/21] refactor(coro): renamed dpp::awaitable to dpp::async NOTE: this commit is part of a PR that was reorganized through rebases, and is very likely to not compile --- .../classes/Generator/CoroGenerator.php | 8 +- docpages/advanced_reference/coroutines.md | 10 +- include/dpp/cluster.h | 14 +- include/dpp/cluster_coro_calls.h | 6 +- include/dpp/cluster_sync_calls.h | 4 +- include/dpp/coro.h | 172 ++++++-- include/dpp/event_router.h | 40 +- src/dpp/cluster/sticker.cpp | 4 +- src/dpp/cluster/timer.cpp | 2 +- src/dpp/cluster_coro_calls.cpp | 384 +++++++++--------- src/dpp/cluster_sync_calls.cpp | 8 +- 11 files changed, 392 insertions(+), 260 deletions(-) diff --git a/buildtools/classes/Generator/CoroGenerator.php b/buildtools/classes/Generator/CoroGenerator.php index faf0d41c72..1ed3604dc2 100644 --- a/buildtools/classes/Generator/CoroGenerator.php +++ b/buildtools/classes/Generator/CoroGenerator.php @@ -98,7 +98,9 @@ public function generateHeaderDef(string $returnType, string $currentFunction, s */ public function generateCppDef(string $returnType, string $currentFunction, string $parameters, string $noDefaults, string $parameterTypes, string $parameterNames): string { - return "awaitable cluster::co_${currentFunction}($noDefaults) {\n\treturn {this, static_cast(&cluster::$currentFunction)$parameterNames};\n}\n\n"; + if (substr($parameterNames, 0, 2) === ", ") + $parameterNames = substr($parameterNames, 2); + return "awaitable cluster::co_${currentFunction}($noDefaults) {\n\treturn [=, this] (auto &&cc) { this->$currentFunction($parameterNames" . (empty($parameterNames) ? "": ", ") . "cc); };\n}\n\n"; } /** @@ -114,7 +116,7 @@ public function getCommentArray(): array */ public function saveHeader(string $content): void { - $content .= "awaitable co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", const std::multimap &headers = {});\n\n"; + $content .= "awaitable co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", std::multimap headers = {});\n\n"; file_put_contents('include/dpp/cluster_coro_calls.h', $content); } @@ -123,7 +125,7 @@ public function saveHeader(string $content): void */ public function saveCpp(string $cppcontent): void { - $cppcontent .= "dpp::awaitable dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers) {\n\treturn awaitable{[&](auto &&cc) { this->request(url, method, cc, postdata, mimetype, headers); }};\n} + $cppcontent .= "dpp::awaitable dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, std::multimap headers) {\n\treturn awaitable{[=, this, h = std::move(headers)](auto &&cc) { this->request(url, method, cc, postdata, mimetype, h); }};\n} #endif "; diff --git a/docpages/advanced_reference/coroutines.md b/docpages/advanced_reference/coroutines.md index 6f4551a8ba..6d21002a9d 100644 --- a/docpages/advanced_reference/coroutines.md +++ b/docpages/advanced_reference/coroutines.md @@ -47,11 +47,11 @@ int main() { 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::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` as seen in the example at line 10. +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` 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. -When an awaitable is `co_await`-ed, 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. +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. -Inside of a `dpp::task`, someone can use `co_return` in place of `return`. +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. \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. 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. @@ -87,7 +87,7 @@ int main() { co_return; } // Send a " is thinking..." message, to wait on later so we can edit - dpp::awaitable thinking = event.co_thinking(false); + dpp::async thinking = event.co_thinking(false); // Download and co_await the result dpp::http_request_completion_t response = co_await cluster->co_request(attachment.url, dpp::m_get); @@ -180,7 +180,7 @@ int main() { }; // Send a " is thinking..." message, to wait on later so we can edit - dpp::awaitable thinking = event.co_thinking(false); + dpp::async thinking = event.co_thinking(false); // Call our coroutine defined above to retrieve the member requested std::optional member = co_await resolve_member(event); diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index b087a25fed..e4a404f46f 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -361,12 +361,12 @@ class DPP_EXPORT cluster { #ifdef DPP_CORO /** - * @brief Start a one-time timer. Use the co_await keyword on its return value to suspend the coroutine until the timer ends - * - * @param seconds How long to run the timer for - * @return awaitable co_await-able object holding the timer_handle + * @brief Get an awaitable to wait a certain amount of seconds. Use the co_await keyword on its return value to suspend the coroutine until the timer ends + * + * @param seconds How long to wait for + * @return awaitable Object that can be co_await-ed to suspend the function for a certain time */ - awaitable co_timer(uint64_t seconds); + awaitable co_sleep(uint64_t seconds); #endif /** @@ -3252,7 +3252,7 @@ class DPP_EXPORT cluster { * @param callback Function to call when the API call completes. * On success the callback will contain a dpp::sticker object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - void guild_sticker_create(sticker &s, command_completion_event_t callback = utility::log_error()); + void guild_sticker_create(const sticker &s, command_completion_event_t callback = utility::log_error()); /** * @brief Modify a sticker in a guild @@ -3262,7 +3262,7 @@ class DPP_EXPORT cluster { * @param callback Function to call when the API call completes. * On success the callback will contain a dpp::sticker object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - void guild_sticker_modify(sticker &s, command_completion_event_t callback = utility::log_error()); + void guild_sticker_modify(const sticker &s, command_completion_event_t callback = utility::log_error()); /** * @brief Delete a sticker from a guild diff --git a/include/dpp/cluster_coro_calls.h b/include/dpp/cluster_coro_calls.h index c468f1ac30..30623d42e8 100644 --- a/include/dpp/cluster_coro_calls.h +++ b/include/dpp/cluster_coro_calls.h @@ -1742,7 +1742,7 @@ awaitable co_stage_instance_delete(const snowflake chan * @return sticker returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_sticker_create(sticker &s); +awaitable co_guild_sticker_create(const sticker &s); /** * @brief Delete a sticker from a guild @@ -1776,7 +1776,7 @@ awaitable co_guild_sticker_get(snowflake id, snowflake * @return sticker returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_sticker_modify(sticker &s); +awaitable co_guild_sticker_modify(const sticker &s); /** * @brief Get all guild stickers @@ -2390,5 +2390,5 @@ awaitable co_get_webhook_with_token(snowflake webhook_i /* End of auto-generated definitions */ -awaitable co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}); +awaitable co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", std::multimap headers = {}); diff --git a/include/dpp/cluster_sync_calls.h b/include/dpp/cluster_sync_calls.h index d431a19f0b..01b59fbf5e 100644 --- a/include/dpp/cluster_sync_calls.h +++ b/include/dpp/cluster_sync_calls.h @@ -2135,7 +2135,7 @@ confirmation stage_instance_delete_sync(const snowflake channel_id); * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -sticker guild_sticker_create_sync(sticker &s); +sticker guild_sticker_create_sync(const sticker &s); /** * @brief Delete a sticker from a guild @@ -2178,7 +2178,7 @@ sticker guild_sticker_get_sync(snowflake id, snowflake guild_id); * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -sticker guild_sticker_modify_sync(sticker &s); +sticker guild_sticker_modify_sync(const sticker &s); /** * @brief Get all guild stickers diff --git a/include/dpp/coro.h b/include/dpp/coro.h index 818369d8b8..d18b88fbaf 100644 --- a/include/dpp/coro.h +++ b/include/dpp/coro.h @@ -23,6 +23,7 @@ namespace std { #include #include +#include #include #include #include @@ -174,7 +175,7 @@ namespace dpp { * @return bool Whether not to suspend the caller or not */ bool await_ready() { - return handle.done(); + return handle.promise().is_sync; } /** @@ -192,8 +193,13 @@ namespace dpp { if (my_promise.is_sync) return false; + + std::lock_guard lock{my_promise.mutex}; + + if (handle.done()) + return (false); my_promise.parent = caller; - caller.promise().is_sync = false; + caller.promise().is_sync = false; return true; } @@ -414,12 +420,120 @@ namespace dpp { if (handle.promise().exception) // If we have an exception, rethrow std::rethrow_exception(handle.promise().exception); if constexpr (!std::is_same_v) // If we have a return type, return it and clean up our stored value - return std::forward(*std::exchange(handle.promise().value, std::nullopt)); + return *std::exchange(handle.promise().value, std::nullopt); } + template + class async; + /** * @brief A co_await-able object handling an API call. * + * @remark - The coroutine may be resumed in another thread, do not rely on thread_local variables. + * @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. + * @tparam ReturnType The return type of the API call. Defaults to confirmation_callback_t + */ + template + struct awaitable { + /** + * @brief Alias for the request wrapped in a callable object. + */ + using request_t = std::function)>; + + /** + * @brief Callable object that will be responsible for the API call when co_await-ing. + */ + request_t request; + + /** + * @brief Construct an awaitable object from a callable. This can be used to manually wrap an async call. + * + * Callable should be an invokeable object, taking a parameter that is the callback to be passed to the async call. + * For example : `[cluster, message](auto &&cb) { cluster->message_create(message, cb); } + * + * @warning This callback is to be executed later, on co_await. Be mindful of reference captures. + */ + awaitable(std::invocable> auto &&fun) : request{fun} {} + + /** + * @brief Copy constructor. + */ + awaitable(const awaitable&) = default; + + /** + * @brief Move constructor. + */ + awaitable(awaitable&&) noexcept = default; + + /** + * @brief Copy assignment operator. + */ + awaitable& operator=(const awaitable&) = default; + + /** + * @brief Move assignment operator. + */ + awaitable& operator=(awaitable&&) noexcept = default; + + /** + * @brief Awaitable object returned by operator co_await. + * + * @warning Do not use this directly, it is made to work with co_await. + */ + struct awaiter { + /** + * @brief Reference to the callable object that will be responsible for the API call when co_await-ing. + */ + const request_t& fun; + + /** + * @brief Optional containing the result of the API call. + */ + std::optional result = std::nullopt; + + /** + * @brief First function called by the standard library when this object is co_await-ed. Returns whether or not we can skip suspending the caller. + * + * @return false Always return false, we send the API call on suspend. + */ + bool await_ready() const noexcept { + return false; + } + + /** + * @brief Second function called by the standard library when this object is co_await-ed. Suspends and sends the API call. + */ + template + void await_suspend(detail::std_coroutine::coroutine_handle caller) noexcept(noexcept(std::invoke(fun, std::declval&&>()))) { + if constexpr (requires (T promise) {{promise.is_sync} -> std::same_as;}) + caller.promise().is_sync = false; + std::invoke(fun, [this, caller](auto &&api_result) { + result = api_result; + caller.resume(); + }); + } + + /** + * @brief Function called by the standard library when the handle is resumed. Returns the API result as an rvalue. + */ + ReturnType await_resume() { + return *std::exchange(result, std::nullopt); + } + }; + + /** + * @brief Overload of co_await for this object, the caller is suspended and the API call is executed. On completion the whole co_await expression evaluates to the result of the API call as an rvalue. + * + * In contrast with dpp::async, it is fine to co_await this object more than once. + */ + auto operator co_await() const noexcept { + return awaiter{request}; + } + }; + + /** + * @brief A co_await-able object handling an API call in parallel with the caller. + * * This class is the return type of the dpp::cluster::co_* methods, but it can also be created manually to wrap any async call. * * @remark - This object's methods, other than constructors and operators, should not be called directly. It is designed to be used with coroutine keywords such as co_await. @@ -428,14 +542,14 @@ namespace dpp { * @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. * @tparam ReturnType The return type of the API call. Defaults to confirmation_callback_t */ - template - class awaitable { + template + class async { /** * @brief Ref-counted callback, contains the callback logic and manages the lifetime of the callback data over multiple threads. */ struct shared_callback { /** - * @brief State of the awaitable and its callback. + * @brief State of the async and its callback. */ struct callback_state { enum state_t { @@ -445,7 +559,7 @@ namespace dpp { }; /** - * @brief Mutex to ensure the API result isn't set at the same time the coroutine is awaited and its value is checked, or the awaitable is destroyed + * @brief Mutex to ensure the API result isn't set at the same time the coroutine is awaited and its value is checked, or the async is destroyed */ std::mutex mutex{}; @@ -480,7 +594,7 @@ namespace dpp { void operator()(const ReturnType &cback) const { std::unique_lock lock{get_mutex()}; - if (state->state == callback_state::dangling) // Awaitable is gone - likely an exception killed it or it was never co_await-ed + if (state->state == callback_state::dangling) // Async object is gone - likely an exception killed it or it was never co_await-ed return; state->result = cback; state->state = callback_state::done; @@ -533,7 +647,7 @@ namespace dpp { } /** - * @brief Function called by the awaitable when it is destroyed when it was never co_awaited, signals to the callback to abort. + * @brief Function called by the async when it is destroyed when it was never co_awaited, signals to the callback to abort. */ void set_dangling() { if (!state) // moved-from object @@ -580,13 +694,13 @@ namespace dpp { }; /** - * @brief Shared state of the awaitable and its callback, to be used across threads. + * @brief Shared state of the async and its callback, to be used across threads. */ shared_callback api_callback; public: /** - * @brief Construct an awaitable wrapping an object method, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. + * @brief Construct an async object wrapping an object method, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. * * @param obj The object to call the method on * @param fun The method of the object to call. Its last parameter must be a callback taking a parameter of type ReturnType @@ -596,12 +710,12 @@ namespace dpp { #ifndef _DOXYGEN_ requires std::invocable> #endif - awaitable(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} { + async(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} { std::invoke(std::forward(fun), std::forward(obj), std::forward(args)..., api_callback); } /** - * @brief Construct an awaitable wrapping an invokeable object, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. + * @brief Construct an async object wrapping an invokeable object, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. * * @param fun The object to call using std::invoke. Its last parameter must be a callable taking a parameter of type ReturnType * @param args Parameters to pass to the object, excluding the callback @@ -610,47 +724,56 @@ namespace dpp { #ifndef _DOXYGEN_ requires std::invocable> #endif - awaitable(Fun &&fun, Args&&... args) : api_callback{} { + async(Fun &&fun, Args&&... args) : api_callback{} { std::invoke(std::forward(fun), std::forward(args)..., api_callback); } + /** + * @brief Construct an async wrapping an awaitable, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. + * + * @param callable The awaitable object whose API call to execute. + */ + async(const awaitable &awaitable) : api_callback{} { + std::invoke(awaitable.callable, api_callback); + } + /** * @brief Destructor. If any callback is pending it will be aborted. * */ - ~awaitable() { + ~async() { api_callback.set_dangling(); } /** * @brief Copy constructor is disabled */ - awaitable(const awaitable &) = delete; + async(const async &) = delete; /** * @brief Move constructor * * 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. * - * @remark Using the moved-from awaitable after this function is undefined behavior. - * @param other The awaitable object to move the data from. + * @remark Using the moved-from async after this function is undefined behavior. + * @param other The async object to move the data from. */ - awaitable(awaitable &&other) noexcept = default; + async(async &&other) noexcept = default; /** * @brief Copy assignment is disabled */ - awaitable &operator=(const awaitable &) = delete; + async &operator=(const async &) = delete; /** * @brief Move assignment operator. * * 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. * - * @remark Using the moved-from awaitable after this function is undefined behavior. - * @param other The awaitable object to move the data from + * @remark Using the moved-from async after this function is undefined behavior. + * @param other The async object to move the data from */ - awaitable &operator=(awaitable &&other) noexcept = default; + async &operator=(async &&other) noexcept = default; /** * @brief First function called by the standard library when the object is co-awaited. @@ -686,12 +809,13 @@ namespace dpp { } /** - * @brief Function called by the standard library when the awaitable is resumed. Its return value is what the whole co_await expression evaluates to + * @brief Function called by the standard library when the async is resumed. Its return value is what the whole co_await expression evaluates to * * @remark Do not call this manually, use the co_await keyword instead. * @return ReturnType The result of the API call. */ ReturnType await_resume() { + // no locking needed here as the callback has already executed return std::move(*api_callback.get_result()); } }; diff --git a/include/dpp/event_router.h b/include/dpp/event_router.h index 7642065655..0ede8a323a 100644 --- a/include/dpp/event_router.h +++ b/include/dpp/event_router.h @@ -108,7 +108,7 @@ template class event_router_t { * * Note: keep a listener's parameter as a value type, the event passed can die while a coroutine is suspended */ - std::map(T)>> coroutine_container; + std::map(const T&)>> coroutine_container; #else #ifndef _DOXYGEN_ /** @@ -164,22 +164,28 @@ template class event_router_t { } }; #ifdef DPP_CORO - auto coro_exception_handler = [from = event.from](std::exception_ptr ptr) { - try { - std::rethrow_exception(ptr); - } - catch (const std::exception &exception) { - if (from && from->creator) - from->creator->log(dpp::loglevel::ll_error, std::string{"Uncaught exception in event coroutine: "} + exception.what()); - } - }; - for (const auto& [_, listener] : coroutine_container) { - if (!event.is_cancelled()) { - dpp::task task = listener(event); + if (!coroutine_container.empty()) { + [](const event_router_t *me, T event) -> dpp::task { + std::vector> coroutines; + auto *cluster = event.from ? event.from->creator : nullptr; - task.on_exception(coro_exception_handler); - } - }; + coroutines.reserve(me->coroutine_container.size()); + for (const auto& [_, listener] : me->coroutine_container) { + if (event.is_cancelled()) + break; + coroutines.emplace_back(listener(event)); + } + for (auto &coro : coroutines) { + try { + co_await coro; + } + catch (const std::exception &e) { + if (cluster) + cluster->log(dpp::loglevel::ll_error, std::string{"Uncaught exception in event coroutine: "} + e.what()); + } + } + }(this, event); + } #endif /* DPP_CORO */ }; @@ -252,7 +258,7 @@ template class event_router_t { * @return event_handle An event handle unique to this event, used to * detach the listener from the event later if necessary. */ - event_handle co_attach(std::function(T)> func) { + event_handle co_attach(std::function(const T&)> func) { std::unique_lock l(lock); event_handle h = next_handle++; coroutine_container.emplace(h, func); diff --git a/src/dpp/cluster/sticker.cpp b/src/dpp/cluster/sticker.cpp index 050a450a45..9f61521a44 100644 --- a/src/dpp/cluster/sticker.cpp +++ b/src/dpp/cluster/sticker.cpp @@ -23,7 +23,7 @@ namespace dpp { -void cluster::guild_sticker_create(sticker &s, command_completion_event_t callback) { +void cluster::guild_sticker_create(const sticker &s, command_completion_event_t callback) { this->post_rest(API_PATH "/guilds", std::to_string(s.guild_id), "stickers", m_post, s.build_json(false), [this, callback](json &j, const http_request_completion_t& http) { if (callback) { callback(confirmation_callback_t(this, sticker().fill_from_json(&j), http)); @@ -39,7 +39,7 @@ void cluster::guild_sticker_get(snowflake id, snowflake guild_id, command_comple rest_request(this, API_PATH "/guilds", std::to_string(guild_id), "stickers/" + std::to_string(id), m_get, "", callback); } -void cluster::guild_sticker_modify(sticker &s, command_completion_event_t callback) { +void cluster::guild_sticker_modify(const sticker &s, command_completion_event_t callback) { rest_request(this, API_PATH "/guilds", std::to_string(s.guild_id), "stickers/" + std::to_string(s.id), m_patch, s.build_json(true), callback); } diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index c168f9021c..3f92d61e52 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -105,7 +105,7 @@ void cluster::tick_timers() { } #ifdef DPP_CORO -awaitable cluster::co_timer(uint64_t seconds) { +awaitable cluster::co_sleep(uint64_t seconds) { return {[this, seconds] (auto &&cb) { start_timer([this, cb](dpp::timer handle) { cb(handle); diff --git a/src/dpp/cluster_coro_calls.cpp b/src/dpp/cluster_coro_calls.cpp index 9b0d46816a..ad23ec92c7 100644 --- a/src/dpp/cluster_coro_calls.cpp +++ b/src/dpp/cluster_coro_calls.cpp @@ -36,763 +36,763 @@ namespace dpp { awaitable cluster::co_global_bulk_command_create(const std::vector &commands) { - return {this, static_cast &, command_completion_event_t)>(&cluster::global_bulk_command_create), commands}; + return [=, this] (auto &&cc) { this->global_bulk_command_create(commands, cc); }; } awaitable cluster::co_global_command_create(const slashcommand &s) { - return {this, static_cast(&cluster::global_command_create), s}; + return [=, this] (auto &&cc) { this->global_command_create(s, cc); }; } awaitable cluster::co_global_command_get(snowflake id) { - return {this, static_cast(&cluster::global_command_get), id}; + return [=, this] (auto &&cc) { this->global_command_get(id, cc); }; } awaitable cluster::co_global_command_delete(snowflake id) { - return {this, static_cast(&cluster::global_command_delete), id}; + return [=, this] (auto &&cc) { this->global_command_delete(id, cc); }; } awaitable cluster::co_global_command_edit(const slashcommand &s) { - return {this, static_cast(&cluster::global_command_edit), s}; + return [=, this] (auto &&cc) { this->global_command_edit(s, cc); }; } awaitable cluster::co_global_commands_get() { - return {this, static_cast(&cluster::global_commands_get)}; + return [=, this] (auto &&cc) { this->global_commands_get(cc); }; } awaitable cluster::co_guild_bulk_command_create(const std::vector &commands, snowflake guild_id) { - return {this, static_cast &, snowflake, command_completion_event_t)>(&cluster::guild_bulk_command_create), commands, guild_id}; + return [=, this] (auto &&cc) { this->guild_bulk_command_create(commands, guild_id, cc); }; } awaitable cluster::co_guild_commands_get_permissions(snowflake guild_id) { - return {this, static_cast(&cluster::guild_commands_get_permissions), guild_id}; + return [=, this] (auto &&cc) { this->guild_commands_get_permissions(guild_id, cc); }; } awaitable cluster::co_guild_bulk_command_edit_permissions(const std::vector &commands, snowflake guild_id) { - return {this, static_cast &, snowflake, command_completion_event_t)>(&cluster::guild_bulk_command_edit_permissions), commands, guild_id}; + return [=, this] (auto &&cc) { this->guild_bulk_command_edit_permissions(commands, guild_id, cc); }; } awaitable cluster::co_guild_command_create(const slashcommand &s, snowflake guild_id) { - return {this, static_cast(&cluster::guild_command_create), s, guild_id}; + return [=, this] (auto &&cc) { this->guild_command_create(s, guild_id, cc); }; } awaitable cluster::co_guild_command_delete(snowflake id, snowflake guild_id) { - return {this, static_cast(&cluster::guild_command_delete), id, guild_id}; + return [=, this] (auto &&cc) { this->guild_command_delete(id, guild_id, cc); }; } awaitable cluster::co_guild_command_edit_permissions(const slashcommand &s, snowflake guild_id) { - return {this, static_cast(&cluster::guild_command_edit_permissions), s, guild_id}; + return [=, this] (auto &&cc) { this->guild_command_edit_permissions(s, guild_id, cc); }; } awaitable cluster::co_guild_command_get(snowflake id, snowflake guild_id) { - return {this, static_cast(&cluster::guild_command_get), id, guild_id}; + return [=, this] (auto &&cc) { this->guild_command_get(id, guild_id, cc); }; } awaitable cluster::co_guild_command_get_permissions(snowflake id, snowflake guild_id) { - return {this, static_cast(&cluster::guild_command_get_permissions), id, guild_id}; + return [=, this] (auto &&cc) { this->guild_command_get_permissions(id, guild_id, cc); }; } awaitable cluster::co_guild_command_edit(const slashcommand &s, snowflake guild_id) { - return {this, static_cast(&cluster::guild_command_edit), s, guild_id}; + return [=, this] (auto &&cc) { this->guild_command_edit(s, guild_id, cc); }; } awaitable cluster::co_guild_commands_get(snowflake guild_id) { - return {this, static_cast(&cluster::guild_commands_get), guild_id}; + return [=, this] (auto &&cc) { this->guild_commands_get(guild_id, cc); }; } awaitable cluster::co_interaction_response_create(snowflake interaction_id, const std::string &token, const interaction_response &r) { - return {this, static_cast(&cluster::interaction_response_create), interaction_id, token, r}; + return [=, this] (auto &&cc) { this->interaction_response_create(interaction_id, token, r, cc); }; } awaitable cluster::co_interaction_response_edit(const std::string &token, const message &m) { - return {this, static_cast(&cluster::interaction_response_edit), token, m}; + return [=, this] (auto &&cc) { this->interaction_response_edit(token, m, cc); }; } awaitable cluster::co_interaction_response_get_original(const std::string &token) { - return {this, static_cast(&cluster::interaction_response_get_original), token}; + return [=, this] (auto &&cc) { this->interaction_response_get_original(token, cc); }; } awaitable cluster::co_interaction_followup_create(const std::string &token, const message &m) { - return {this, static_cast(&cluster::interaction_followup_create), token, m}; + return [=, this] (auto &&cc) { this->interaction_followup_create(token, m, cc); }; } awaitable cluster::co_interaction_followup_edit_original(const std::string &token, const message &m) { - return {this, static_cast(&cluster::interaction_followup_edit_original), token, m}; + return [=, this] (auto &&cc) { this->interaction_followup_edit_original(token, m, cc); }; } awaitable cluster::co_interaction_followup_delete(const std::string &token) { - return {this, static_cast(&cluster::interaction_followup_delete), token}; + return [=, this] (auto &&cc) { this->interaction_followup_delete(token, cc); }; } awaitable cluster::co_interaction_followup_edit(const std::string &token, const message &m) { - return {this, static_cast(&cluster::interaction_followup_edit), token, m}; + return [=, this] (auto &&cc) { this->interaction_followup_edit(token, m, cc); }; } awaitable cluster::co_interaction_followup_get(const std::string &token, snowflake message_id) { - return {this, static_cast(&cluster::interaction_followup_get), token, message_id}; + return [=, this] (auto &&cc) { this->interaction_followup_get(token, message_id, cc); }; } awaitable cluster::co_interaction_followup_get_original(const std::string &token) { - return {this, static_cast(&cluster::interaction_followup_get_original), token}; + return [=, this] (auto &&cc) { this->interaction_followup_get_original(token, cc); }; } awaitable cluster::co_automod_rules_get(snowflake guild_id) { - return {this, static_cast(&cluster::automod_rules_get), guild_id}; + return [=, this] (auto &&cc) { this->automod_rules_get(guild_id, cc); }; } awaitable cluster::co_automod_rule_get(snowflake guild_id, snowflake rule_id) { - return {this, static_cast(&cluster::automod_rule_get), guild_id, rule_id}; + return [=, this] (auto &&cc) { this->automod_rule_get(guild_id, rule_id, cc); }; } awaitable cluster::co_automod_rule_create(snowflake guild_id, const automod_rule& r) { - return {this, static_cast(&cluster::automod_rule_create), guild_id, r}; + return [=, this] (auto &&cc) { this->automod_rule_create(guild_id, r, cc); }; } awaitable cluster::co_automod_rule_edit(snowflake guild_id, const automod_rule& r) { - return {this, static_cast(&cluster::automod_rule_edit), guild_id, r}; + return [=, this] (auto &&cc) { this->automod_rule_edit(guild_id, r, cc); }; } awaitable cluster::co_automod_rule_delete(snowflake guild_id, snowflake rule_id) { - return {this, static_cast(&cluster::automod_rule_delete), guild_id, rule_id}; + return [=, this] (auto &&cc) { this->automod_rule_delete(guild_id, rule_id, cc); }; } awaitable cluster::co_channel_create(const class channel &c) { - return {this, static_cast(&cluster::channel_create), c}; + return [=, this] (auto &&cc) { this->channel_create(c, cc); }; } awaitable cluster::co_channel_delete_permission(const class channel &c, snowflake overwrite_id) { - return {this, static_cast(&cluster::channel_delete_permission), c, overwrite_id}; + return [=, this] (auto &&cc) { this->channel_delete_permission(c, overwrite_id, cc); }; } awaitable cluster::co_channel_delete(snowflake channel_id) { - return {this, static_cast(&cluster::channel_delete), channel_id}; + return [=, this] (auto &&cc) { this->channel_delete(channel_id, cc); }; } awaitable cluster::co_channel_edit_permissions(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { - return {this, static_cast(&cluster::channel_edit_permissions), c, overwrite_id, allow, deny, member}; + return [=, this] (auto &&cc) { this->channel_edit_permissions(c, overwrite_id, allow, deny, member, cc); }; } awaitable cluster::co_channel_edit_permissions(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { - return {this, static_cast(&cluster::channel_edit_permissions), channel_id, overwrite_id, allow, deny, member}; + return [=, this] (auto &&cc) { this->channel_edit_permissions(channel_id, overwrite_id, allow, deny, member, cc); }; } awaitable cluster::co_channel_edit_positions(const std::vector &c) { - return {this, static_cast &, command_completion_event_t)>(&cluster::channel_edit_positions), c}; + return [=, this] (auto &&cc) { this->channel_edit_positions(c, cc); }; } awaitable cluster::co_channel_edit(const class channel &c) { - return {this, static_cast(&cluster::channel_edit), c}; + return [=, this] (auto &&cc) { this->channel_edit(c, cc); }; } awaitable cluster::co_channel_follow_news(const class channel &c, snowflake target_channel_id) { - return {this, static_cast(&cluster::channel_follow_news), c, target_channel_id}; + return [=, this] (auto &&cc) { this->channel_follow_news(c, target_channel_id, cc); }; } awaitable cluster::co_channel_get(snowflake c) { - return {this, static_cast(&cluster::channel_get), c}; + return [=, this] (auto &&cc) { this->channel_get(c, cc); }; } awaitable cluster::co_channel_invite_create(const class channel &c, const class invite &i) { - return {this, static_cast(&cluster::channel_invite_create), c, i}; + return [=, this] (auto &&cc) { this->channel_invite_create(c, i, cc); }; } awaitable cluster::co_channel_invites_get(const class channel &c) { - return {this, static_cast(&cluster::channel_invites_get), c}; + return [=, this] (auto &&cc) { this->channel_invites_get(c, cc); }; } awaitable cluster::co_channel_typing(const class channel &c) { - return {this, static_cast(&cluster::channel_typing), c}; + return [=, this] (auto &&cc) { this->channel_typing(c, cc); }; } awaitable cluster::co_channel_typing(snowflake cid) { - return {this, static_cast(&cluster::channel_typing), cid}; + return [=, this] (auto &&cc) { this->channel_typing(cid, cc); }; } awaitable cluster::co_channels_get(snowflake guild_id) { - return {this, static_cast(&cluster::channels_get), guild_id}; + return [=, this] (auto &&cc) { this->channels_get(guild_id, cc); }; } awaitable cluster::co_create_dm_channel(snowflake user_id) { - return {this, static_cast(&cluster::create_dm_channel), user_id}; + return [=, this] (auto &&cc) { this->create_dm_channel(user_id, cc); }; } awaitable cluster::co_current_user_get_dms() { - return {this, static_cast(&cluster::current_user_get_dms)}; + return [=, this] (auto &&cc) { this->current_user_get_dms(cc); }; } awaitable cluster::co_direct_message_create(snowflake user_id, const message &m) { - return {this, static_cast(&cluster::direct_message_create), user_id, m}; + return [=, this] (auto &&cc) { this->direct_message_create(user_id, m, cc); }; } awaitable cluster::co_gdm_add(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick) { - return {this, static_cast(&cluster::gdm_add), channel_id, user_id, access_token, nick}; + return [=, this] (auto &&cc) { this->gdm_add(channel_id, user_id, access_token, nick, cc); }; } awaitable cluster::co_gdm_remove(snowflake channel_id, snowflake user_id) { - return {this, static_cast(&cluster::gdm_remove), channel_id, user_id}; + return [=, this] (auto &&cc) { this->gdm_remove(channel_id, user_id, cc); }; } awaitable cluster::co_guild_emoji_create(snowflake guild_id, const class emoji& newemoji) { - return {this, static_cast(&cluster::guild_emoji_create), guild_id, newemoji}; + return [=, this] (auto &&cc) { this->guild_emoji_create(guild_id, newemoji, cc); }; } awaitable cluster::co_guild_emoji_delete(snowflake guild_id, snowflake emoji_id) { - return {this, static_cast(&cluster::guild_emoji_delete), guild_id, emoji_id}; + return [=, this] (auto &&cc) { this->guild_emoji_delete(guild_id, emoji_id, cc); }; } awaitable cluster::co_guild_emoji_edit(snowflake guild_id, const class emoji& newemoji) { - return {this, static_cast(&cluster::guild_emoji_edit), guild_id, newemoji}; + return [=, this] (auto &&cc) { this->guild_emoji_edit(guild_id, newemoji, cc); }; } awaitable cluster::co_guild_emoji_get(snowflake guild_id, snowflake emoji_id) { - return {this, static_cast(&cluster::guild_emoji_get), guild_id, emoji_id}; + return [=, this] (auto &&cc) { this->guild_emoji_get(guild_id, emoji_id, cc); }; } awaitable cluster::co_guild_emojis_get(snowflake guild_id) { - return {this, static_cast(&cluster::guild_emojis_get), guild_id}; + return [=, this] (auto &&cc) { this->guild_emojis_get(guild_id, cc); }; } awaitable cluster::co_get_gateway_bot() { - return {this, static_cast(&cluster::get_gateway_bot)}; + return [=, this] (auto &&cc) { this->get_gateway_bot(cc); }; } awaitable cluster::co_guild_current_member_edit(snowflake guild_id, const std::string &nickname) { - return {this, static_cast(&cluster::guild_current_member_edit), guild_id, nickname}; + return [=, this] (auto &&cc) { this->guild_current_member_edit(guild_id, nickname, cc); }; } awaitable cluster::co_guild_auditlog_get(snowflake guild_id, snowflake user_id, uint32_t action_type, snowflake before, snowflake after, uint32_t limit) { - return {this, static_cast(&cluster::guild_auditlog_get), guild_id, user_id, action_type, before, after, limit}; + return [=, this] (auto &&cc) { this->guild_auditlog_get(guild_id, user_id, action_type, before, after, limit, cc); }; } awaitable cluster::co_guild_ban_add(snowflake guild_id, snowflake user_id, uint32_t delete_message_seconds) { - return {this, static_cast(&cluster::guild_ban_add), guild_id, user_id, delete_message_seconds}; + return [=, this] (auto &&cc) { this->guild_ban_add(guild_id, user_id, delete_message_seconds, cc); }; } awaitable cluster::co_guild_ban_delete(snowflake guild_id, snowflake user_id) { - return {this, static_cast(&cluster::guild_ban_delete), guild_id, user_id}; + return [=, this] (auto &&cc) { this->guild_ban_delete(guild_id, user_id, cc); }; } awaitable cluster::co_guild_create(const class guild &g) { - return {this, static_cast(&cluster::guild_create), g}; + return [=, this] (auto &&cc) { this->guild_create(g, cc); }; } awaitable cluster::co_guild_delete(snowflake guild_id) { - return {this, static_cast(&cluster::guild_delete), guild_id}; + return [=, this] (auto &&cc) { this->guild_delete(guild_id, cc); }; } awaitable cluster::co_guild_delete_integration(snowflake guild_id, snowflake integration_id) { - return {this, static_cast(&cluster::guild_delete_integration), guild_id, integration_id}; + return [=, this] (auto &&cc) { this->guild_delete_integration(guild_id, integration_id, cc); }; } awaitable cluster::co_guild_edit(const class guild &g) { - return {this, static_cast(&cluster::guild_edit), g}; + return [=, this] (auto &&cc) { this->guild_edit(g, cc); }; } awaitable cluster::co_guild_edit_widget(snowflake guild_id, const class guild_widget &gw) { - return {this, static_cast(&cluster::guild_edit_widget), guild_id, gw}; + return [=, this] (auto &&cc) { this->guild_edit_widget(guild_id, gw, cc); }; } awaitable cluster::co_guild_get_ban(snowflake guild_id, snowflake user_id) { - return {this, static_cast(&cluster::guild_get_ban), guild_id, user_id}; + return [=, this] (auto &&cc) { this->guild_get_ban(guild_id, user_id, cc); }; } awaitable cluster::co_guild_get_bans(snowflake guild_id, snowflake before, snowflake after, snowflake limit) { - return {this, static_cast(&cluster::guild_get_bans), guild_id, before, after, limit}; + return [=, this] (auto &&cc) { this->guild_get_bans(guild_id, before, after, limit, cc); }; } awaitable cluster::co_guild_get(snowflake guild_id) { - return {this, static_cast(&cluster::guild_get), guild_id}; + return [=, this] (auto &&cc) { this->guild_get(guild_id, cc); }; } awaitable cluster::co_guild_get_integrations(snowflake guild_id) { - return {this, static_cast(&cluster::guild_get_integrations), guild_id}; + return [=, this] (auto &&cc) { this->guild_get_integrations(guild_id, cc); }; } awaitable cluster::co_guild_get_preview(snowflake guild_id) { - return {this, static_cast(&cluster::guild_get_preview), guild_id}; + return [=, this] (auto &&cc) { this->guild_get_preview(guild_id, cc); }; } awaitable cluster::co_guild_get_vanity(snowflake guild_id) { - return {this, static_cast(&cluster::guild_get_vanity), guild_id}; + return [=, this] (auto &&cc) { this->guild_get_vanity(guild_id, cc); }; } awaitable cluster::co_guild_get_widget(snowflake guild_id) { - return {this, static_cast(&cluster::guild_get_widget), guild_id}; + return [=, this] (auto &&cc) { this->guild_get_widget(guild_id, cc); }; } awaitable cluster::co_guild_modify_integration(snowflake guild_id, const class integration &i) { - return {this, static_cast(&cluster::guild_modify_integration), guild_id, i}; + return [=, this] (auto &&cc) { this->guild_modify_integration(guild_id, i, cc); }; } awaitable cluster::co_guild_get_prune_counts(snowflake guild_id, const struct prune& pruneinfo) { - return {this, static_cast(&cluster::guild_get_prune_counts), guild_id, pruneinfo}; + return [=, this] (auto &&cc) { this->guild_get_prune_counts(guild_id, pruneinfo, cc); }; } awaitable cluster::co_guild_begin_prune(snowflake guild_id, const struct prune& pruneinfo) { - return {this, static_cast(&cluster::guild_begin_prune), guild_id, pruneinfo}; + return [=, this] (auto &&cc) { this->guild_begin_prune(guild_id, pruneinfo, cc); }; } awaitable cluster::co_guild_set_nickname(snowflake guild_id, const std::string &nickname) { - return {this, static_cast(&cluster::guild_set_nickname), guild_id, nickname}; + return [=, this] (auto &&cc) { this->guild_set_nickname(guild_id, nickname, cc); }; } awaitable cluster::co_guild_sync_integration(snowflake guild_id, snowflake integration_id) { - return {this, static_cast(&cluster::guild_sync_integration), guild_id, integration_id}; + return [=, this] (auto &&cc) { this->guild_sync_integration(guild_id, integration_id, cc); }; } awaitable cluster::co_guild_get_onboarding(snowflake guild_id) { - return {this, static_cast(&cluster::guild_get_onboarding), guild_id}; + return [=, this] (auto &&cc) { this->guild_get_onboarding(guild_id, cc); }; } awaitable cluster::co_guild_edit_onboarding(const struct onboarding& o) { - return {this, static_cast(&cluster::guild_edit_onboarding), o}; + return [=, this] (auto &&cc) { this->guild_edit_onboarding(o, cc); }; } awaitable cluster::co_guild_get_welcome_screen(snowflake guild_id) { - return {this, static_cast(&cluster::guild_get_welcome_screen), guild_id}; + return [=, this] (auto &&cc) { this->guild_get_welcome_screen(guild_id, cc); }; } awaitable cluster::co_guild_edit_welcome_screen(snowflake guild_id, const struct welcome_screen& welcome_screen, bool enabled) { - return {this, static_cast(&cluster::guild_edit_welcome_screen), guild_id, welcome_screen, enabled}; + return [=, this] (auto &&cc) { this->guild_edit_welcome_screen(guild_id, welcome_screen, enabled, cc); }; } awaitable cluster::co_guild_add_member(const guild_member& gm, const std::string &access_token) { - return {this, static_cast(&cluster::guild_add_member), gm, access_token}; + return [=, this] (auto &&cc) { this->guild_add_member(gm, access_token, cc); }; } awaitable cluster::co_guild_edit_member(const guild_member& gm) { - return {this, static_cast(&cluster::guild_edit_member), gm}; + return [=, this] (auto &&cc) { this->guild_edit_member(gm, cc); }; } awaitable cluster::co_guild_get_member(snowflake guild_id, snowflake user_id) { - return {this, static_cast(&cluster::guild_get_member), guild_id, user_id}; + return [=, this] (auto &&cc) { this->guild_get_member(guild_id, user_id, cc); }; } awaitable cluster::co_guild_get_members(snowflake guild_id, uint16_t limit, snowflake after) { - return {this, static_cast(&cluster::guild_get_members), guild_id, limit, after}; + return [=, this] (auto &&cc) { this->guild_get_members(guild_id, limit, after, cc); }; } awaitable cluster::co_guild_member_add_role(snowflake guild_id, snowflake user_id, snowflake role_id) { - return {this, static_cast(&cluster::guild_member_add_role), guild_id, user_id, role_id}; + return [=, this] (auto &&cc) { this->guild_member_add_role(guild_id, user_id, role_id, cc); }; } awaitable cluster::co_guild_member_delete(snowflake guild_id, snowflake user_id) { - return {this, static_cast(&cluster::guild_member_delete), guild_id, user_id}; + return [=, this] (auto &&cc) { this->guild_member_delete(guild_id, user_id, cc); }; } awaitable cluster::co_guild_member_kick(snowflake guild_id, snowflake user_id) { - return {this, static_cast(&cluster::guild_member_kick), guild_id, user_id}; + return [=, this] (auto &&cc) { this->guild_member_kick(guild_id, user_id, cc); }; } awaitable cluster::co_guild_member_timeout(snowflake guild_id, snowflake user_id, time_t communication_disabled_until) { - return {this, static_cast(&cluster::guild_member_timeout), guild_id, user_id, communication_disabled_until}; + return [=, this] (auto &&cc) { this->guild_member_timeout(guild_id, user_id, communication_disabled_until, cc); }; } awaitable cluster::co_guild_member_delete_role(snowflake guild_id, snowflake user_id, snowflake role_id) { - return {this, static_cast(&cluster::guild_member_delete_role), guild_id, user_id, role_id}; + return [=, this] (auto &&cc) { this->guild_member_delete_role(guild_id, user_id, role_id, cc); }; } awaitable cluster::co_guild_member_remove_role(snowflake guild_id, snowflake user_id, snowflake role_id) { - return {this, static_cast(&cluster::guild_member_remove_role), guild_id, user_id, role_id}; + return [=, this] (auto &&cc) { this->guild_member_remove_role(guild_id, user_id, role_id, cc); }; } awaitable cluster::co_guild_member_move(const snowflake channel_id, const snowflake guild_id, const snowflake user_id) { - return {this, static_cast(&cluster::guild_member_move), channel_id, guild_id, user_id}; + return [=, this] (auto &&cc) { this->guild_member_move(channel_id, guild_id, user_id, cc); }; } awaitable cluster::co_guild_search_members(snowflake guild_id, const std::string& query, uint16_t limit) { - return {this, static_cast(&cluster::guild_search_members), guild_id, query, limit}; + return [=, this] (auto &&cc) { this->guild_search_members(guild_id, query, limit, cc); }; } awaitable cluster::co_guild_get_invites(snowflake guild_id) { - return {this, static_cast(&cluster::guild_get_invites), guild_id}; + return [=, this] (auto &&cc) { this->guild_get_invites(guild_id, cc); }; } awaitable cluster::co_invite_delete(const std::string &invitecode) { - return {this, static_cast(&cluster::invite_delete), invitecode}; + return [=, this] (auto &&cc) { this->invite_delete(invitecode, cc); }; } awaitable cluster::co_invite_get(const std::string &invite_code) { - return {this, static_cast(&cluster::invite_get), invite_code}; + return [=, this] (auto &&cc) { this->invite_get(invite_code, cc); }; } awaitable cluster::co_message_add_reaction(const struct message &m, const std::string &reaction) { - return {this, static_cast(&cluster::message_add_reaction), m, reaction}; + return [=, this] (auto &&cc) { this->message_add_reaction(m, reaction, cc); }; } awaitable cluster::co_message_add_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return {this, static_cast(&cluster::message_add_reaction), message_id, channel_id, reaction}; + return [=, this] (auto &&cc) { this->message_add_reaction(message_id, channel_id, reaction, cc); }; } awaitable cluster::co_message_create(const message &m) { - return {this, static_cast(&cluster::message_create), m}; + return [=, this] (auto &&cc) { this->message_create(m, cc); }; } awaitable cluster::co_message_crosspost(snowflake message_id, snowflake channel_id) { - return {this, static_cast(&cluster::message_crosspost), message_id, channel_id}; + return [=, this] (auto &&cc) { this->message_crosspost(message_id, channel_id, cc); }; } awaitable cluster::co_message_delete_all_reactions(const struct message &m) { - return {this, static_cast(&cluster::message_delete_all_reactions), m}; + return [=, this] (auto &&cc) { this->message_delete_all_reactions(m, cc); }; } awaitable cluster::co_message_delete_all_reactions(snowflake message_id, snowflake channel_id) { - return {this, static_cast(&cluster::message_delete_all_reactions), message_id, channel_id}; + return [=, this] (auto &&cc) { this->message_delete_all_reactions(message_id, channel_id, cc); }; } awaitable cluster::co_message_delete_bulk(const std::vector& message_ids, snowflake channel_id) { - return {this, static_cast&, snowflake, command_completion_event_t)>(&cluster::message_delete_bulk), message_ids, channel_id}; + return [=, this] (auto &&cc) { this->message_delete_bulk(message_ids, channel_id, cc); }; } awaitable cluster::co_message_delete(snowflake message_id, snowflake channel_id) { - return {this, static_cast(&cluster::message_delete), message_id, channel_id}; + return [=, this] (auto &&cc) { this->message_delete(message_id, channel_id, cc); }; } awaitable cluster::co_message_delete_own_reaction(const struct message &m, const std::string &reaction) { - return {this, static_cast(&cluster::message_delete_own_reaction), m, reaction}; + return [=, this] (auto &&cc) { this->message_delete_own_reaction(m, reaction, cc); }; } awaitable cluster::co_message_delete_own_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return {this, static_cast(&cluster::message_delete_own_reaction), message_id, channel_id, reaction}; + return [=, this] (auto &&cc) { this->message_delete_own_reaction(message_id, channel_id, reaction, cc); }; } awaitable cluster::co_message_delete_reaction(const struct message &m, snowflake user_id, const std::string &reaction) { - return {this, static_cast(&cluster::message_delete_reaction), m, user_id, reaction}; + return [=, this] (auto &&cc) { this->message_delete_reaction(m, user_id, reaction, cc); }; } awaitable cluster::co_message_delete_reaction(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction) { - return {this, static_cast(&cluster::message_delete_reaction), message_id, channel_id, user_id, reaction}; + return [=, this] (auto &&cc) { this->message_delete_reaction(message_id, channel_id, user_id, reaction, cc); }; } awaitable cluster::co_message_delete_reaction_emoji(const struct message &m, const std::string &reaction) { - return {this, static_cast(&cluster::message_delete_reaction_emoji), m, reaction}; + return [=, this] (auto &&cc) { this->message_delete_reaction_emoji(m, reaction, cc); }; } awaitable cluster::co_message_delete_reaction_emoji(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return {this, static_cast(&cluster::message_delete_reaction_emoji), message_id, channel_id, reaction}; + return [=, this] (auto &&cc) { this->message_delete_reaction_emoji(message_id, channel_id, reaction, cc); }; } awaitable cluster::co_message_edit(const message &m) { - return {this, static_cast(&cluster::message_edit), m}; + return [=, this] (auto &&cc) { this->message_edit(m, cc); }; } awaitable cluster::co_message_get(snowflake message_id, snowflake channel_id) { - return {this, static_cast(&cluster::message_get), message_id, channel_id}; + return [=, this] (auto &&cc) { this->message_get(message_id, channel_id, cc); }; } awaitable cluster::co_message_get_reactions(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { - return {this, static_cast(&cluster::message_get_reactions), m, reaction, before, after, limit}; + return [=, this] (auto &&cc) { this->message_get_reactions(m, reaction, before, after, limit, cc); }; } awaitable cluster::co_message_get_reactions(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { - return {this, static_cast(&cluster::message_get_reactions), message_id, channel_id, reaction, before, after, limit}; + return [=, this] (auto &&cc) { this->message_get_reactions(message_id, channel_id, reaction, before, after, limit, cc); }; } awaitable cluster::co_message_pin(snowflake channel_id, snowflake message_id) { - return {this, static_cast(&cluster::message_pin), channel_id, message_id}; + return [=, this] (auto &&cc) { this->message_pin(channel_id, message_id, cc); }; } awaitable cluster::co_messages_get(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit) { - return {this, static_cast(&cluster::messages_get), channel_id, around, before, after, limit}; + return [=, this] (auto &&cc) { this->messages_get(channel_id, around, before, after, limit, cc); }; } awaitable cluster::co_message_unpin(snowflake channel_id, snowflake message_id) { - return {this, static_cast(&cluster::message_unpin), channel_id, message_id}; + return [=, this] (auto &&cc) { this->message_unpin(channel_id, message_id, cc); }; } awaitable cluster::co_channel_pins_get(snowflake channel_id) { - return {this, static_cast(&cluster::channel_pins_get), channel_id}; + return [=, this] (auto &&cc) { this->channel_pins_get(channel_id, cc); }; } awaitable cluster::co_role_create(const class role &r) { - return {this, static_cast(&cluster::role_create), r}; + return [=, this] (auto &&cc) { this->role_create(r, cc); }; } awaitable cluster::co_role_delete(snowflake guild_id, snowflake role_id) { - return {this, static_cast(&cluster::role_delete), guild_id, role_id}; + return [=, this] (auto &&cc) { this->role_delete(guild_id, role_id, cc); }; } awaitable cluster::co_role_edit(const class role &r) { - return {this, static_cast(&cluster::role_edit), r}; + return [=, this] (auto &&cc) { this->role_edit(r, cc); }; } awaitable cluster::co_roles_edit_position(snowflake guild_id, const std::vector &roles) { - return {this, static_cast &, command_completion_event_t)>(&cluster::roles_edit_position), guild_id, roles}; + return [=, this] (auto &&cc) { this->roles_edit_position(guild_id, roles, cc); }; } awaitable cluster::co_roles_get(snowflake guild_id) { - return {this, static_cast(&cluster::roles_get), guild_id}; + return [=, this] (auto &&cc) { this->roles_get(guild_id, cc); }; } awaitable cluster::co_application_role_connection_get(snowflake application_id) { - return {this, static_cast(&cluster::application_role_connection_get), application_id}; + return [=, this] (auto &&cc) { this->application_role_connection_get(application_id, cc); }; } awaitable cluster::co_application_role_connection_update(snowflake application_id, const std::vector &connection_metadata) { - return {this, static_cast &, command_completion_event_t)>(&cluster::application_role_connection_update), application_id, connection_metadata}; + return [=, this] (auto &&cc) { this->application_role_connection_update(application_id, connection_metadata, cc); }; } awaitable cluster::co_user_application_role_connection_get(snowflake application_id) { - return {this, static_cast(&cluster::user_application_role_connection_get), application_id}; + return [=, this] (auto &&cc) { this->user_application_role_connection_get(application_id, cc); }; } awaitable cluster::co_user_application_role_connection_update(snowflake application_id, const application_role_connection &connection) { - return {this, static_cast(&cluster::user_application_role_connection_update), application_id, connection}; + return [=, this] (auto &&cc) { this->user_application_role_connection_update(application_id, connection, cc); }; } awaitable cluster::co_guild_events_get(snowflake guild_id) { - return {this, static_cast(&cluster::guild_events_get), guild_id}; + return [=, this] (auto &&cc) { this->guild_events_get(guild_id, cc); }; } awaitable cluster::co_guild_event_create(const scheduled_event& event) { - return {this, static_cast(&cluster::guild_event_create), event}; + return [=, this] (auto &&cc) { this->guild_event_create(event, cc); }; } awaitable cluster::co_guild_event_delete(snowflake event_id, snowflake guild_id) { - return {this, static_cast(&cluster::guild_event_delete), event_id, guild_id}; + return [=, this] (auto &&cc) { this->guild_event_delete(event_id, guild_id, cc); }; } awaitable cluster::co_guild_event_edit(const scheduled_event& event) { - return {this, static_cast(&cluster::guild_event_edit), event}; + return [=, this] (auto &&cc) { this->guild_event_edit(event, cc); }; } awaitable cluster::co_guild_event_get(snowflake guild_id, snowflake event_id) { - return {this, static_cast(&cluster::guild_event_get), guild_id, event_id}; + return [=, this] (auto &&cc) { this->guild_event_get(guild_id, event_id, cc); }; } awaitable cluster::co_stage_instance_create(const stage_instance& si) { - return {this, static_cast(&cluster::stage_instance_create), si}; + return [=, this] (auto &&cc) { this->stage_instance_create(si, cc); }; } awaitable cluster::co_stage_instance_get(const snowflake channel_id) { - return {this, static_cast(&cluster::stage_instance_get), channel_id}; + return [=, this] (auto &&cc) { this->stage_instance_get(channel_id, cc); }; } awaitable cluster::co_stage_instance_edit(const stage_instance& si) { - return {this, static_cast(&cluster::stage_instance_edit), si}; + return [=, this] (auto &&cc) { this->stage_instance_edit(si, cc); }; } awaitable cluster::co_stage_instance_delete(const snowflake channel_id) { - return {this, static_cast(&cluster::stage_instance_delete), channel_id}; + return [=, this] (auto &&cc) { this->stage_instance_delete(channel_id, cc); }; } -awaitable cluster::co_guild_sticker_create(sticker &s) { - return {this, static_cast(&cluster::guild_sticker_create), s}; +awaitable cluster::co_guild_sticker_create(const sticker &s) { + return [=, this] (auto &&cc) { this->guild_sticker_create(s, cc); }; } awaitable cluster::co_guild_sticker_delete(snowflake sticker_id, snowflake guild_id) { - return {this, static_cast(&cluster::guild_sticker_delete), sticker_id, guild_id}; + return [=, this] (auto &&cc) { this->guild_sticker_delete(sticker_id, guild_id, cc); }; } awaitable cluster::co_guild_sticker_get(snowflake id, snowflake guild_id) { - return {this, static_cast(&cluster::guild_sticker_get), id, guild_id}; + return [=, this] (auto &&cc) { this->guild_sticker_get(id, guild_id, cc); }; } -awaitable cluster::co_guild_sticker_modify(sticker &s) { - return {this, static_cast(&cluster::guild_sticker_modify), s}; +awaitable cluster::co_guild_sticker_modify(const sticker &s) { + return [=, this] (auto &&cc) { this->guild_sticker_modify(s, cc); }; } awaitable cluster::co_guild_stickers_get(snowflake guild_id) { - return {this, static_cast(&cluster::guild_stickers_get), guild_id}; + return [=, this] (auto &&cc) { this->guild_stickers_get(guild_id, cc); }; } awaitable cluster::co_nitro_sticker_get(snowflake id) { - return {this, static_cast(&cluster::nitro_sticker_get), id}; + return [=, this] (auto &&cc) { this->nitro_sticker_get(id, cc); }; } awaitable cluster::co_sticker_packs_get() { - return {this, static_cast(&cluster::sticker_packs_get)}; + return [=, this] (auto &&cc) { this->sticker_packs_get(cc); }; } awaitable cluster::co_guild_create_from_template(const std::string &code, const std::string &name) { - return {this, static_cast(&cluster::guild_create_from_template), code, name}; + return [=, this] (auto &&cc) { this->guild_create_from_template(code, name, cc); }; } awaitable cluster::co_guild_template_create(snowflake guild_id, const std::string &name, const std::string &description) { - return {this, static_cast(&cluster::guild_template_create), guild_id, name, description}; + return [=, this] (auto &&cc) { this->guild_template_create(guild_id, name, description, cc); }; } awaitable cluster::co_guild_template_delete(snowflake guild_id, const std::string &code) { - return {this, static_cast(&cluster::guild_template_delete), guild_id, code}; + return [=, this] (auto &&cc) { this->guild_template_delete(guild_id, code, cc); }; } awaitable cluster::co_guild_template_modify(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description) { - return {this, static_cast(&cluster::guild_template_modify), guild_id, code, name, description}; + return [=, this] (auto &&cc) { this->guild_template_modify(guild_id, code, name, description, cc); }; } awaitable cluster::co_guild_templates_get(snowflake guild_id) { - return {this, static_cast(&cluster::guild_templates_get), guild_id}; + return [=, this] (auto &&cc) { this->guild_templates_get(guild_id, cc); }; } awaitable cluster::co_guild_template_sync(snowflake guild_id, const std::string &code) { - return {this, static_cast(&cluster::guild_template_sync), guild_id, code}; + return [=, this] (auto &&cc) { this->guild_template_sync(guild_id, code, cc); }; } awaitable cluster::co_template_get(const std::string &code) { - return {this, static_cast(&cluster::template_get), code}; + return [=, this] (auto &&cc) { this->template_get(code, cc); }; } awaitable cluster::co_current_user_join_thread(snowflake thread_id) { - return {this, static_cast(&cluster::current_user_join_thread), thread_id}; + return [=, this] (auto &&cc) { this->current_user_join_thread(thread_id, cc); }; } awaitable cluster::co_current_user_leave_thread(snowflake thread_id) { - return {this, static_cast(&cluster::current_user_leave_thread), thread_id}; + return [=, this] (auto &&cc) { this->current_user_leave_thread(thread_id, cc); }; } awaitable cluster::co_threads_get_active(snowflake guild_id) { - return {this, static_cast(&cluster::threads_get_active), guild_id}; + return [=, this] (auto &&cc) { this->threads_get_active(guild_id, cc); }; } awaitable cluster::co_threads_get_joined_private_archived(snowflake channel_id, snowflake before_id, uint16_t limit) { - return {this, static_cast(&cluster::threads_get_joined_private_archived), channel_id, before_id, limit}; + return [=, this] (auto &&cc) { this->threads_get_joined_private_archived(channel_id, before_id, limit, cc); }; } awaitable cluster::co_threads_get_private_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit) { - return {this, static_cast(&cluster::threads_get_private_archived), channel_id, before_timestamp, limit}; + return [=, this] (auto &&cc) { this->threads_get_private_archived(channel_id, before_timestamp, limit, cc); }; } awaitable cluster::co_threads_get_public_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit) { - return {this, static_cast(&cluster::threads_get_public_archived), channel_id, before_timestamp, limit}; + return [=, this] (auto &&cc) { this->threads_get_public_archived(channel_id, before_timestamp, limit, cc); }; } awaitable cluster::co_thread_member_get(const snowflake thread_id, const snowflake user_id) { - return {this, static_cast(&cluster::thread_member_get), thread_id, user_id}; + return [=, this] (auto &&cc) { this->thread_member_get(thread_id, user_id, cc); }; } awaitable cluster::co_thread_members_get(snowflake thread_id) { - return {this, static_cast(&cluster::thread_members_get), thread_id}; + return [=, this] (auto &&cc) { this->thread_members_get(thread_id, cc); }; } awaitable cluster::co_thread_create_in_forum(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags) { - return {this, static_cast, command_completion_event_t)>(&cluster::thread_create_in_forum), thread_name, channel_id, msg, auto_archive_duration, rate_limit_per_user, applied_tags}; + return [=, this] (auto &&cc) { this->thread_create_in_forum(thread_name, channel_id, msg, auto_archive_duration, rate_limit_per_user, applied_tags, cc); }; } awaitable cluster::co_thread_create(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user) { - return {this, static_cast(&cluster::thread_create), thread_name, channel_id, auto_archive_duration, thread_type, invitable, rate_limit_per_user}; + return [=, this] (auto &&cc) { this->thread_create(thread_name, channel_id, auto_archive_duration, thread_type, invitable, rate_limit_per_user, cc); }; } awaitable cluster::co_thread_edit(const thread &t) { - return {this, static_cast(&cluster::thread_edit), t}; + return [=, this] (auto &&cc) { this->thread_edit(t, cc); }; } awaitable cluster::co_thread_create_with_message(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user) { - return {this, static_cast(&cluster::thread_create_with_message), thread_name, channel_id, message_id, auto_archive_duration, rate_limit_per_user}; + return [=, this] (auto &&cc) { this->thread_create_with_message(thread_name, channel_id, message_id, auto_archive_duration, rate_limit_per_user, cc); }; } awaitable cluster::co_thread_member_add(snowflake thread_id, snowflake user_id) { - return {this, static_cast(&cluster::thread_member_add), thread_id, user_id}; + return [=, this] (auto &&cc) { this->thread_member_add(thread_id, user_id, cc); }; } awaitable cluster::co_thread_member_remove(snowflake thread_id, snowflake user_id) { - return {this, static_cast(&cluster::thread_member_remove), thread_id, user_id}; + return [=, this] (auto &&cc) { this->thread_member_remove(thread_id, user_id, cc); }; } awaitable cluster::co_current_user_edit(const std::string &nickname, const std::string& image_blob, const image_type type) { - return {this, static_cast(&cluster::current_user_edit), nickname, image_blob, type}; + return [=, this] (auto &&cc) { this->current_user_edit(nickname, image_blob, type, cc); }; } awaitable cluster::co_current_application_get() { - return {this, static_cast(&cluster::current_application_get)}; + return [=, this] (auto &&cc) { this->current_application_get(cc); }; } awaitable cluster::co_current_user_get() { - return {this, static_cast(&cluster::current_user_get)}; + return [=, this] (auto &&cc) { this->current_user_get(cc); }; } awaitable cluster::co_current_user_set_voice_state(snowflake guild_id, snowflake channel_id, bool suppress, time_t request_to_speak_timestamp) { - return {this, static_cast(&cluster::current_user_set_voice_state), guild_id, channel_id, suppress, request_to_speak_timestamp}; + return [=, this] (auto &&cc) { this->current_user_set_voice_state(guild_id, channel_id, suppress, request_to_speak_timestamp, cc); }; } awaitable cluster::co_user_set_voice_state(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress) { - return {this, static_cast(&cluster::user_set_voice_state), user_id, guild_id, channel_id, suppress}; + return [=, this] (auto &&cc) { this->user_set_voice_state(user_id, guild_id, channel_id, suppress, cc); }; } awaitable cluster::co_current_user_connections_get() { - return {this, static_cast(&cluster::current_user_connections_get)}; + return [=, this] (auto &&cc) { this->current_user_connections_get(cc); }; } awaitable cluster::co_current_user_get_guilds() { - return {this, static_cast(&cluster::current_user_get_guilds)}; + return [=, this] (auto &&cc) { this->current_user_get_guilds(cc); }; } awaitable cluster::co_current_user_leave_guild(snowflake guild_id) { - return {this, static_cast(&cluster::current_user_leave_guild), guild_id}; + return [=, this] (auto &&cc) { this->current_user_leave_guild(guild_id, cc); }; } awaitable cluster::co_user_get(snowflake user_id) { - return {this, static_cast(&cluster::user_get), user_id}; + return [=, this] (auto &&cc) { this->user_get(user_id, cc); }; } awaitable cluster::co_user_get_cached(snowflake user_id) { - return {this, static_cast(&cluster::user_get_cached), user_id}; + return [=, this] (auto &&cc) { this->user_get_cached(user_id, cc); }; } awaitable cluster::co_get_voice_regions() { - return {this, static_cast(&cluster::get_voice_regions)}; + return [=, this] (auto &&cc) { this->get_voice_regions(cc); }; } awaitable cluster::co_guild_get_voice_regions(snowflake guild_id) { - return {this, static_cast(&cluster::guild_get_voice_regions), guild_id}; + return [=, this] (auto &&cc) { this->guild_get_voice_regions(guild_id, cc); }; } awaitable cluster::co_create_webhook(const class webhook &w) { - return {this, static_cast(&cluster::create_webhook), w}; + return [=, this] (auto &&cc) { this->create_webhook(w, cc); }; } awaitable cluster::co_delete_webhook(snowflake webhook_id) { - return {this, static_cast(&cluster::delete_webhook), webhook_id}; + return [=, this] (auto &&cc) { this->delete_webhook(webhook_id, cc); }; } awaitable cluster::co_delete_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id) { - return {this, static_cast(&cluster::delete_webhook_message), wh, message_id, thread_id}; + return [=, this] (auto &&cc) { this->delete_webhook_message(wh, message_id, thread_id, cc); }; } awaitable cluster::co_delete_webhook_with_token(snowflake webhook_id, const std::string &token) { - return {this, static_cast(&cluster::delete_webhook_with_token), webhook_id, token}; + return [=, this] (auto &&cc) { this->delete_webhook_with_token(webhook_id, token, cc); }; } awaitable cluster::co_edit_webhook(const class webhook& wh) { - return {this, static_cast(&cluster::edit_webhook), wh}; + return [=, this] (auto &&cc) { this->edit_webhook(wh, cc); }; } awaitable cluster::co_edit_webhook_message(const class webhook &wh, const struct message& m, snowflake thread_id) { - return {this, static_cast(&cluster::edit_webhook_message), wh, m, thread_id}; + return [=, this] (auto &&cc) { this->edit_webhook_message(wh, m, thread_id, cc); }; } awaitable cluster::co_edit_webhook_with_token(const class webhook& wh) { - return {this, static_cast(&cluster::edit_webhook_with_token), wh}; + return [=, this] (auto &&cc) { this->edit_webhook_with_token(wh, cc); }; } awaitable cluster::co_execute_webhook(const class webhook &wh, const struct message& m, bool wait, snowflake thread_id, const std::string& thread_name) { - return {this, static_cast(&cluster::execute_webhook), wh, m, wait, thread_id, thread_name}; + return [=, this] (auto &&cc) { this->execute_webhook(wh, m, wait, thread_id, thread_name, cc); }; } awaitable cluster::co_get_channel_webhooks(snowflake channel_id) { - return {this, static_cast(&cluster::get_channel_webhooks), channel_id}; + return [=, this] (auto &&cc) { this->get_channel_webhooks(channel_id, cc); }; } awaitable cluster::co_get_guild_webhooks(snowflake guild_id) { - return {this, static_cast(&cluster::get_guild_webhooks), guild_id}; + return [=, this] (auto &&cc) { this->get_guild_webhooks(guild_id, cc); }; } awaitable cluster::co_get_webhook(snowflake webhook_id) { - return {this, static_cast(&cluster::get_webhook), webhook_id}; + return [=, this] (auto &&cc) { this->get_webhook(webhook_id, cc); }; } awaitable cluster::co_get_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id) { - return {this, static_cast(&cluster::get_webhook_message), wh, message_id, thread_id}; + return [=, this] (auto &&cc) { this->get_webhook_message(wh, message_id, thread_id, cc); }; } awaitable cluster::co_get_webhook_with_token(snowflake webhook_id, const std::string &token) { - return {this, static_cast(&cluster::get_webhook_with_token), webhook_id, token}; + return [=, this] (auto &&cc) { this->get_webhook_with_token(webhook_id, token, cc); }; } }; /* End of auto-generated definitions */ -dpp::awaitable dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers) { - return awaitable{[&](auto &&cc) { this->request(url, method, cc, postdata, mimetype, headers); }}; +dpp::awaitable dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, std::multimap headers) { + return awaitable{[=, this, h = std::move(headers)](auto &&cc) { this->request(url, method, cc, postdata, mimetype, h); }}; } #endif diff --git a/src/dpp/cluster_sync_calls.cpp b/src/dpp/cluster_sync_calls.cpp index 2b9be8b862..1e45f6d736 100644 --- a/src/dpp/cluster_sync_calls.cpp +++ b/src/dpp/cluster_sync_calls.cpp @@ -573,8 +573,8 @@ confirmation cluster::stage_instance_delete_sync(const snowflake channel_id) { return dpp::sync(this, static_cast(&cluster::stage_instance_delete), channel_id); } -sticker cluster::guild_sticker_create_sync(sticker &s) { - return dpp::sync(this, static_cast(&cluster::guild_sticker_create), s); +sticker cluster::guild_sticker_create_sync(const sticker &s) { + return dpp::sync(this, static_cast(&cluster::guild_sticker_create), s); } confirmation cluster::guild_sticker_delete_sync(snowflake sticker_id, snowflake guild_id) { @@ -585,8 +585,8 @@ sticker cluster::guild_sticker_get_sync(snowflake id, snowflake guild_id) { return dpp::sync(this, static_cast(&cluster::guild_sticker_get), id, guild_id); } -sticker cluster::guild_sticker_modify_sync(sticker &s) { - return dpp::sync(this, static_cast(&cluster::guild_sticker_modify), s); +sticker cluster::guild_sticker_modify_sync(const sticker &s) { + return dpp::sync(this, static_cast(&cluster::guild_sticker_modify), s); } sticker_map cluster::guild_stickers_get_sync(snowflake guild_id) { From 29cd0f036f7a3df03eed24e277707dc033584dad Mon Sep 17 00:00:00 2001 From: Amber Ehrlich Date: Thu, 10 Aug 2023 12:14:20 -0400 Subject: [PATCH 08/21] feat(coro): dpp::job, a lightweight async coroutine --- include/dpp/coro.h | 128 +++++++++++++++++++++++++++++++++++-- include/dpp/event_router.h | 32 +++------- 2 files changed, 130 insertions(+), 30 deletions(-) diff --git a/include/dpp/coro.h b/include/dpp/coro.h index d18b88fbaf..a766bc7b55 100644 --- a/include/dpp/coro.h +++ b/include/dpp/coro.h @@ -1,3 +1,24 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * Copyright 2022 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + #ifdef DPP_CORO #pragma once @@ -188,7 +209,7 @@ namespace dpp { * @return bool Whether to suspend the caller or not */ template - bool await_suspend(detail::task_handle caller) noexcept { + bool await_suspend(detail::std_coroutine::coroutine_handle caller) { auto &my_promise = handle.promise(); if (my_promise.is_sync) @@ -198,8 +219,9 @@ namespace dpp { if (handle.done()) return (false); + if constexpr (requires (T t) { t.is_sync = false; }) + caller.promise().is_sync = false; my_promise.parent = caller; - caller.promise().is_sync = false; return true; } @@ -423,6 +445,85 @@ namespace dpp { return *std::exchange(handle.promise().value, std::nullopt); } + /** + * @brief Extremely light coroutine object designed to send off a coroutine to execute on its own. + * + * This object is extremely light, and is the preferred way to use coroutines if you do not need to co_await the result. + * + * @warning It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing. + * At this point, if the function returns, every object declared in the function including its parameters are destroyed, which causes dangling references. + * This is exactly the same problem as references in lambdas : https://dpp.dev/lambdas-and-locals.html. + * For this reason, `co_await` will error if any parameters are passed by reference. + * If you must pass a reference, pass it as a pointer or with std::ref, but you must fully understand the reason behind this warning, and what to avoid. + * If you prefer a safer type, use `coroutine` for synchronous execution, or `task` for parallel tasks, and co_await them. + */ + class job {}; + + namespace detail { + /** + * @brief Coroutine promise type for a job + */ + template + struct job_promise { + /* + * @brief Function called when the job is done. + * + * @return Do not suspend at the end, destroying the handle immediately + */ + std_coroutine::suspend_never final_suspend() const noexcept { + return {}; + } + + /* + * @brief Function called when the job is started. + * + * @return Do not suspend at the start, starting the job immediately + */ + std_coroutine::suspend_never initial_suspend() const noexcept { + return {}; + } + + /** + * @brief Function called to get the job object + * + * @return job + */ + dpp::job get_return_object() const noexcept { + return {}; + } + + /** + * @brief Function called when an exception is thrown and not caught. + * + * @throw Immediately rethrows the exception to the caller / resumer + */ + void unhandled_exception() const noexcept(false) { + throw; + } + + /** + * @brief Function called when the job returns. Does nothing. + */ + void return_void() const noexcept {} + + template + T await_transform(T &&expr) const noexcept { + /** + * `job` is extremely efficient as a coroutine but this comes with drawbacks : + * It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing. + * At this point, if the function returns, every object declared in the function including its parameters are destroyed, which causes dangling references. + * This is exactly the same problem as references in lambdas : https://dpp.dev/lambdas-and-locals.html. + * + * If you must pass a reference, pass it as a pointer or with std::ref, but you must fully understand the reason behind this warning, and what to avoid. + * If you prefer a safer type, use `coroutine` for synchronous execution, or `task` for parallel tasks, and co_await them. + */ + static_assert(!has_reference_params, "co_await is disabled in dpp::job when taking parameters by reference. read comment above this line for more info"); + + return std::forward(expr); + } + }; + } + template class async; @@ -734,7 +835,7 @@ namespace dpp { * @param callable The awaitable object whose API call to execute. */ async(const awaitable &awaitable) : api_callback{} { - std::invoke(awaitable.callable, api_callback); + std::invoke(awaitable.request, api_callback); } /** @@ -798,13 +899,14 @@ namespace dpp { * @param handle The handle to the coroutine co_await-ing and being suspended */ template - bool await_suspend(detail::task_handle handle) { + bool await_suspend(detail::std_coroutine::coroutine_handle caller) { std::lock_guard lock{api_callback.get_mutex()}; if (api_callback.get_result().has_value()) return false; // immediately resume the coroutine as we already have the result of the api call - handle.promise().is_sync = false; - api_callback.state->coro_handle = handle; + if constexpr (requires (T t) { t.is_sync = false; }) + caller.promise().is_sync = false; + api_callback.state->coro_handle = caller; return true; // suspend the caller, the callback will resume it } @@ -829,4 +931,18 @@ struct dpp::detail::std_coroutine::coroutine_traits, Args...> { using promise_type = dpp::detail::task_promise; }; +/** + * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. + */ +template +struct dpp::detail::std_coroutine::coroutine_traits { + /** + * @brief Promise type for this coroutine signature. + * + * When the coroutine is created from a lambda, that lambda is passed as a first parameter. + * Not ideal but we'll allow any callable that takes the rest of the arguments passed + */ + using promise_type = dpp::detail::job_promise<(std::is_reference_v || ... || (std::is_reference_v && !std::is_invocable_v))>; +}; + #endif diff --git a/include/dpp/event_router.h b/include/dpp/event_router.h index 0ede8a323a..db208dfc21 100644 --- a/include/dpp/event_router.h +++ b/include/dpp/event_router.h @@ -108,7 +108,7 @@ template class event_router_t { * * Note: keep a listener's parameter as a value type, the event passed can die while a coroutine is suspended */ - std::map(const T&)>> coroutine_container; + std::map> coroutine_container; #else #ifndef _DOXYGEN_ /** @@ -164,27 +164,10 @@ template class event_router_t { } }; #ifdef DPP_CORO - if (!coroutine_container.empty()) { - [](const event_router_t *me, T event) -> dpp::task { - std::vector> coroutines; - auto *cluster = event.from ? event.from->creator : nullptr; - - coroutines.reserve(me->coroutine_container.size()); - for (const auto& [_, listener] : me->coroutine_container) { - if (event.is_cancelled()) - break; - coroutines.emplace_back(listener(event)); - } - for (auto &coro : coroutines) { - try { - co_await coro; - } - catch (const std::exception &e) { - if (cluster) - cluster->log(dpp::loglevel::ll_error, std::string{"Uncaught exception in event coroutine: "} + e.what()); - } - } - }(this, event); + for (const auto& [_, listener] : coroutine_container) { + if (!event.is_cancelled()) { + listener(event); + } } #endif /* DPP_CORO */ }; @@ -254,11 +237,12 @@ template class event_router_t { * the event object and should take exactly one parameter derived * from event_dispatch_t. * - * @param func Coroutine task to attack to the event + * @param func Coroutine task to attack to the event. It MUST take the event by value. * @return event_handle An event handle unique to this event, used to * detach the listener from the event later if necessary. */ - event_handle co_attach(std::function(const T&)> func) { + event_handle co_attach(std::function func) { + // ^ If this errors here - your event handler must take its parameter by VALUE std::unique_lock l(lock); event_handle h = next_handle++; coroutine_container.emplace(h, func); From f8dbd582dbed2e01947c9f1332ce9c5e9261b3a2 Mon Sep 17 00:00:00 2001 From: Amber Ehrlich Date: Thu, 10 Aug 2023 12:17:46 -0400 Subject: [PATCH 09/21] build(coro): add "resumer" to spelling checks --- .cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.cspell.json b/.cspell.json index a0bb0dcb45..fb86cbdf55 100644 --- a/.cspell.json +++ b/.cspell.json @@ -99,6 +99,7 @@ "stringified", "disdppgloss", "awaiter", + "resumer", "checkered", "ramen", "dango", From d662ee35c40c1715f0a04c9f0ec470c4acac4855 Mon Sep 17 00:00:00 2001 From: Amber Ehrlich Date: Thu, 10 Aug 2023 14:19:24 -0400 Subject: [PATCH 10/21] style(coro): split coro.h into its own folder --- include/dpp/cluster.h | 4 +- include/dpp/coro.h | 927 +------------------------------------ include/dpp/coro/async.h | 327 +++++++++++++ include/dpp/coro/coro.h | 86 ++++ include/dpp/coro/job.h | 129 ++++++ include/dpp/coro/task.h | 418 +++++++++++++++++ include/dpp/event_router.h | 3 +- 7 files changed, 966 insertions(+), 928 deletions(-) create mode 100644 include/dpp/coro/async.h create mode 100644 include/dpp/coro/coro.h create mode 100644 include/dpp/coro/job.h create mode 100644 include/dpp/coro/task.h diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index e4a404f46f..96303e71be 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -46,10 +46,8 @@ #include #include #include -#include #include - - +#include namespace dpp { diff --git a/include/dpp/coro.h b/include/dpp/coro.h index a766bc7b55..bfd3ad6120 100644 --- a/include/dpp/coro.h +++ b/include/dpp/coro.h @@ -22,927 +22,8 @@ #ifdef DPP_CORO #pragma once -#if (defined(_LIBCPP_VERSION) and !defined(__cpp_impl_coroutine)) // if libc++ experimental implementation (LLVM < 14) -# define STDCORO_EXPERIMENTAL_HEADER -# define STDCORO_EXPERIMENTAL_NAMESPACE -#endif +#include "coro/async.h" +#include "coro/job.h" +#include "coro/task.h" -#ifdef STDCORO_GLIBCXX_COMPAT -# define __cpp_impl_coroutine 1 -namespace std { - namespace experimental { - using namespace std; - } -} -#endif - -#ifdef STDCORO_EXPERIMENTAL_HEADER -# include -#else -# include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace dpp { - class cluster; - struct confirmation_callback_t; - - /** - * @brief Implementation details for internal use only. - * - * @attention This is only meant to be used by D++ internally. Support will not be given regarding the facilities in this namespace. - */ - namespace detail { -#ifdef _DOXYGEN_ - /** - * @brief Alias for either std or std::experimental depending on compiler and library. Used by coroutine implementation. - * - * @todo Remove and use std when all supported libraries have coroutines in it - */ - namespace std_coroutine {} -#else -# ifdef STDCORO_EXPERIMENTAL_NAMESPACE - namespace std_coroutine = std::experimental; -# else - namespace std_coroutine = std; -# endif -#endif - - /** - * @brief A task's promise type, with special logic for handling nested tasks. - */ - template - struct task_promise; - - /** - * @brief The object automatically co_await-ed at the end of a task. Ensures nested task chains are resolved, and the promise cleans up if it needs to. - */ - template - struct task_chain_final_awaiter; - - /** - * @brief Alias for std::coroutine_handle for a task_promise. - */ - template - using task_handle = detail::std_coroutine::coroutine_handle>; - } // namespace detail - - /** - * @brief A coroutine task. It can be co_awaited to make nested coroutines. - * - * Can be used in conjunction with coroutine events via dpp::event_router_t::co_attach, or on its own. - * - * @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. - * @tparam ReturnType Return type of the coroutine. Can be void, or a complete object that supports move construction and move assignment. - */ - template -#ifndef _DOXYGEN_ - requires std::is_same_v || (!std::is_reference_v && std::is_move_constructible_v && std::is_move_assignable_v) -#endif - class task { - /** - * @brief The coroutine handle of this task. - */ - detail::task_handle handle; - - /** - * @brief Promise type of this coroutine. For internal use only, do not use. - */ - friend struct detail::task_promise; - - /** - * @brief Construct from a coroutine handle. Internal use only - */ - explicit task(detail::task_handle handle_) : handle(handle_) {} - - public: - /** - * @brief Default constructor, creates a task not bound to a coroutine. - */ - task() = default; - - /** - * @brief Copy constructor is disabled - */ - task(const task &) = delete; - - /** - * @brief Move constructor, grabs another task's coroutine handle - * - * @param other Task to move the handle from - */ - task(task &&other) noexcept : handle(std::exchange(other.handle, nullptr)) {} - - - /** - * @brief Destructor. - * - * Destroys the handle if coroutine is done, otherwise detaches it from this thread. - * In detached mode, the handle will destroy itself at the end of the coroutine. - */ - ~task() { - if (handle) { - auto &promise = handle.promise(); - - if (!promise.is_sync) { - std::unique_lock lock{promise.mutex}; - - if (promise.destroy) // promise in async thread checked first and skipped clean up, we do it - { - if (promise.exception && promise.exception_handler) - promise.exception_handler(promise.exception); - lock.unlock(); - handle.destroy(); - } - else - handle.promise().destroy = true; - } - else { - if (promise.exception && promise.exception_handler) - promise.exception_handler(promise.exception); - handle.destroy(); - } - } - } - - /** - * @brief Copy assignment is disabled - */ - task &operator=(const task &) = delete; - - /** - * @brief Move assignment, grabs another task's coroutine handle - * - * @param other Task to move the handle from - */ - task &operator=(task &&other) noexcept { - handle = std::exchange(other.handle, nullptr); - return (*this); - } - - /** - * @brief First function called by the standard library when the task is co_await-ed. - * - * @remark Do not call this manually, use the co_await keyword instead. - * @return bool Whether not to suspend the caller or not - */ - bool await_ready() { - return handle.promise().is_sync; - } - - /** - * @brief Second function called by the standard library when the task is co_await-ed, if await_ready returned false. - * - * Stores the calling coroutine in the promise to resume when this task suspends. - * - * @remark Do not call this manually, use the co_await keyword instead. - * @param caller The calling coroutine, now suspended - * @return bool Whether to suspend the caller or not - */ - template - bool await_suspend(detail::std_coroutine::coroutine_handle caller) { - auto &my_promise = handle.promise(); - - if (my_promise.is_sync) - return false; - - std::lock_guard lock{my_promise.mutex}; - - if (handle.done()) - return (false); - if constexpr (requires (T t) { t.is_sync = false; }) - caller.promise().is_sync = false; - my_promise.parent = caller; - return true; - } - - /** - * @brief Function called by the standard library when the coroutine is resumed. - * - * @remark Do not call this manually, use the co_await keyword instead. - * @throw Throws any exception thrown or uncaught by the coroutine - * @return ReturnType The result of the coroutine. It is the value the whole co-await expression evaluates to - */ - ReturnType await_resume(); - - /** - * @brief Function to check if the coroutine has finished its execution entirely - * - * @return bool Whether the coroutine is done. - * @see https://en.cppreference.com/w/cpp/coroutine/coroutine_handle/done - */ - bool done() const noexcept { - return handle.done(); - } - - /** - * @brief Set the exception handling function. Called when an exception is thrown but not caught - * - * @warning The exception handler must not throw. If an exception that is not caught is thrown in a detached task, the program will terminate. - */ - task &on_exception(std::function func) { - handle.promise().exception_handler = std::move(func); - if (handle.promise().exception) - func(handle.promise().exception); - return *this; - } - }; - - namespace detail { - /** - * @brief Awaitable returned from task_promise's final_suspend. Resumes the parent and cleans up its handle if needed - */ - template - struct task_chain_final_awaiter { - /** - * @brief Always suspend at the end of the task. This allows us to clean up and resume the parent - */ - bool await_ready() noexcept { - return (false); - } - - /* - * @brief The suspension logic of the coroutine when it finishes. Always suspend the caller, meaning cleaning up the handle is on us - * - * @param handle The handle of this coroutine - */ - void await_suspend(detail::task_handle handle) noexcept; - - /* - * @brief Function called when this object is co_awaited by the standard library at the end of final_suspend. Do nothing, return nothing - */ - void await_resume() noexcept {} - }; - /** - * @brief Base implementation of task_promise, without the logic that would depend on the return type. Meant to be inherited from - */ - struct task_promise_base { - /** - * @brief Mutex for async task destruction. - */ - std::mutex mutex{}; - - /** - * @brief Parent coroutine to return to for nested coroutines. - */ - detail::std_coroutine::coroutine_handle<> parent = nullptr; - - /** - * @brief Exception ptr if any was thrown during the coroutine - * - * @see std::exception_ptr - */ - std::exception_ptr exception = nullptr; - - /** - * @brief Whether the coroutine has async calls or not - * - * Will only ever change on the calling thread while callback mutex guards the async thread - */ - bool is_sync = true; - - /** - * @brief Whether either the task object or the promise is gone and the next one to end will clean up - */ - bool destroy = false; - - /** - * @brief Function object called when an exception is thrown from a coroutine - */ - std::function exception_handler = nullptr; - - /** - * @brief Function called by the standard library when the coroutine is created. - * - * @return std::suspend_never Don't suspend, the coroutine starts immediately. - */ - std_coroutine::suspend_never initial_suspend() noexcept { - return {}; - } - - /** - * @brief Function called by the standard library when an exception is thrown and not caught in the coroutine. - * - * Stores the exception pointer to rethrow later - */ - void unhandled_exception() { - exception = std::current_exception(); - } - }; - - /** - * @brief Implementation of task_promise for non-void return type - */ - template - struct task_promise : task_promise_base { - /** - * @brief Stored return value of the coroutine. - * - * @details The main reason we use std::optional here and not ReturnType is to avoid default construction of the value so we only require ReturnType to have a move constructor, instead of both a default constructor and move assignment operator - */ - std::optional value = std::nullopt; - - /** - * @brief Function called by the standard library when the coroutine co_returns a value. - * - * Stores the value internally to hand to the caller when it resumes. - * - * @param expr The value given to co_return - */ - void return_value(ReturnType expr) { - value = std::move(expr); - } - - /** - * @brief Function called by the standard library when the coroutine is created. - * - * @return task The coroutine object - */ - task get_return_object() { - return task{task_handle::from_promise(*this)}; - } - - /** - * @brief Function called by the standard library when the coroutine reaches its last suspension point - * - * @return task_chain_final_awaiter Special object containing the chain resolution and clean-up logic. - */ - task_chain_final_awaiter final_suspend() noexcept { - return {}; - } - }; - - /** - * @brief Implementation of task_promise for void return type - */ - template <> - struct task_promise : task_promise_base { - /** - * @brief Function called by the standard library when the coroutine co_returns - * - * Does nothing but is required by the standard library. - */ - void return_void() {} - - /** - * @brief Function called by the standard library when the coroutine is created. - * - * @return task The coroutine object - */ - task get_return_object() { - return task{task_handle::from_promise(*this)}; - } - - /** - * @brief Function called by the standard library when the coroutine reaches its last suspension point - * - * @return task_chain_final_awaiter Special object containing the chain resolution and clean-up logic. - */ - task_chain_final_awaiter final_suspend() noexcept { - return {}; - } - }; - - template - void detail::task_chain_final_awaiter::await_suspend(detail::task_handle handle) noexcept { - task_promise &promise = handle.promise(); - std_coroutine::coroutine_handle<> parent = promise.parent; - - if (!promise.is_sync) { - std::unique_lock lock{promise.mutex}; - - if (promise.destroy) { - if (promise.exception && promise.exception_handler) - promise.exception_handler(promise.exception); - lock.unlock(); - handle.destroy(); - } - else - promise.destroy = true; // Send the destruction back to the task - } - if (parent) - parent.resume(); - } - } // namespace detail - - template -#ifndef _DOXYGEN_ - requires std::is_same_v || (!std::is_reference_v && std::is_move_constructible_v && std::is_move_assignable_v) -#endif - ReturnType task::await_resume() { - if (handle.promise().exception) // If we have an exception, rethrow - std::rethrow_exception(handle.promise().exception); - if constexpr (!std::is_same_v) // If we have a return type, return it and clean up our stored value - return *std::exchange(handle.promise().value, std::nullopt); - } - - /** - * @brief Extremely light coroutine object designed to send off a coroutine to execute on its own. - * - * This object is extremely light, and is the preferred way to use coroutines if you do not need to co_await the result. - * - * @warning It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing. - * At this point, if the function returns, every object declared in the function including its parameters are destroyed, which causes dangling references. - * This is exactly the same problem as references in lambdas : https://dpp.dev/lambdas-and-locals.html. - * For this reason, `co_await` will error if any parameters are passed by reference. - * If you must pass a reference, pass it as a pointer or with std::ref, but you must fully understand the reason behind this warning, and what to avoid. - * If you prefer a safer type, use `coroutine` for synchronous execution, or `task` for parallel tasks, and co_await them. - */ - class job {}; - - namespace detail { - /** - * @brief Coroutine promise type for a job - */ - template - struct job_promise { - /* - * @brief Function called when the job is done. - * - * @return Do not suspend at the end, destroying the handle immediately - */ - std_coroutine::suspend_never final_suspend() const noexcept { - return {}; - } - - /* - * @brief Function called when the job is started. - * - * @return Do not suspend at the start, starting the job immediately - */ - std_coroutine::suspend_never initial_suspend() const noexcept { - return {}; - } - - /** - * @brief Function called to get the job object - * - * @return job - */ - dpp::job get_return_object() const noexcept { - return {}; - } - - /** - * @brief Function called when an exception is thrown and not caught. - * - * @throw Immediately rethrows the exception to the caller / resumer - */ - void unhandled_exception() const noexcept(false) { - throw; - } - - /** - * @brief Function called when the job returns. Does nothing. - */ - void return_void() const noexcept {} - - template - T await_transform(T &&expr) const noexcept { - /** - * `job` is extremely efficient as a coroutine but this comes with drawbacks : - * It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing. - * At this point, if the function returns, every object declared in the function including its parameters are destroyed, which causes dangling references. - * This is exactly the same problem as references in lambdas : https://dpp.dev/lambdas-and-locals.html. - * - * If you must pass a reference, pass it as a pointer or with std::ref, but you must fully understand the reason behind this warning, and what to avoid. - * If you prefer a safer type, use `coroutine` for synchronous execution, or `task` for parallel tasks, and co_await them. - */ - static_assert(!has_reference_params, "co_await is disabled in dpp::job when taking parameters by reference. read comment above this line for more info"); - - return std::forward(expr); - } - }; - } - - template - class async; - - /** - * @brief A co_await-able object handling an API call. - * - * @remark - The coroutine may be resumed in another thread, do not rely on thread_local variables. - * @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. - * @tparam ReturnType The return type of the API call. Defaults to confirmation_callback_t - */ - template - struct awaitable { - /** - * @brief Alias for the request wrapped in a callable object. - */ - using request_t = std::function)>; - - /** - * @brief Callable object that will be responsible for the API call when co_await-ing. - */ - request_t request; - - /** - * @brief Construct an awaitable object from a callable. This can be used to manually wrap an async call. - * - * Callable should be an invokeable object, taking a parameter that is the callback to be passed to the async call. - * For example : `[cluster, message](auto &&cb) { cluster->message_create(message, cb); } - * - * @warning This callback is to be executed later, on co_await. Be mindful of reference captures. - */ - awaitable(std::invocable> auto &&fun) : request{fun} {} - - /** - * @brief Copy constructor. - */ - awaitable(const awaitable&) = default; - - /** - * @brief Move constructor. - */ - awaitable(awaitable&&) noexcept = default; - - /** - * @brief Copy assignment operator. - */ - awaitable& operator=(const awaitable&) = default; - - /** - * @brief Move assignment operator. - */ - awaitable& operator=(awaitable&&) noexcept = default; - - /** - * @brief Awaitable object returned by operator co_await. - * - * @warning Do not use this directly, it is made to work with co_await. - */ - struct awaiter { - /** - * @brief Reference to the callable object that will be responsible for the API call when co_await-ing. - */ - const request_t& fun; - - /** - * @brief Optional containing the result of the API call. - */ - std::optional result = std::nullopt; - - /** - * @brief First function called by the standard library when this object is co_await-ed. Returns whether or not we can skip suspending the caller. - * - * @return false Always return false, we send the API call on suspend. - */ - bool await_ready() const noexcept { - return false; - } - - /** - * @brief Second function called by the standard library when this object is co_await-ed. Suspends and sends the API call. - */ - template - void await_suspend(detail::std_coroutine::coroutine_handle caller) noexcept(noexcept(std::invoke(fun, std::declval&&>()))) { - if constexpr (requires (T promise) {{promise.is_sync} -> std::same_as;}) - caller.promise().is_sync = false; - std::invoke(fun, [this, caller](auto &&api_result) { - result = api_result; - caller.resume(); - }); - } - - /** - * @brief Function called by the standard library when the handle is resumed. Returns the API result as an rvalue. - */ - ReturnType await_resume() { - return *std::exchange(result, std::nullopt); - } - }; - - /** - * @brief Overload of co_await for this object, the caller is suspended and the API call is executed. On completion the whole co_await expression evaluates to the result of the API call as an rvalue. - * - * In contrast with dpp::async, it is fine to co_await this object more than once. - */ - auto operator co_await() const noexcept { - return awaiter{request}; - } - }; - - /** - * @brief A co_await-able object handling an API call in parallel with the caller. - * - * This class is the return type of the dpp::cluster::co_* methods, but it can also be created manually to wrap any async call. - * - * @remark - This object's methods, other than constructors and operators, should not be called directly. It is designed to be used with coroutine keywords such as co_await. - * @remark - This object must not be co_await-ed more than once. - * @remark - The coroutine may be resumed in another thread, do not rely on thread_local variables. - * @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. - * @tparam ReturnType The return type of the API call. Defaults to confirmation_callback_t - */ - template - class async { - /** - * @brief Ref-counted callback, contains the callback logic and manages the lifetime of the callback data over multiple threads. - */ - struct shared_callback { - /** - * @brief State of the async and its callback. - */ - struct callback_state { - enum state_t { - waiting, - done, - dangling - }; - - /** - * @brief Mutex to ensure the API result isn't set at the same time the coroutine is awaited and its value is checked, or the async is destroyed - */ - std::mutex mutex{}; - - /** - * @brief Number of references to this callback state. - */ - int ref_count; - - /** - * @brief State of the awaitable and the API callback - */ - state_t state = waiting; - - /** - * @brief The stored result of the API call - */ - std::optional result = std::nullopt; - - /** - * @brief Handle to the coroutine co_await-ing on this API call - * - * @see std::coroutine_handle - */ - detail::std_coroutine::coroutine_handle<> coro_handle = nullptr; - }; - - /** - * @brief Callback function. - * - * @param cback The result of the API call. - */ - void operator()(const ReturnType &cback) const { - std::unique_lock lock{get_mutex()}; - - if (state->state == callback_state::dangling) // Async object is gone - likely an exception killed it or it was never co_await-ed - return; - state->result = cback; - state->state = callback_state::done; - if (state->coro_handle) { - auto handle = state->coro_handle; - state->coro_handle = nullptr; - lock.unlock(); - handle.resume(); - } - } - - /** - * @brief Main constructor, allocates a new callback_state object. - */ - shared_callback() : state{new callback_state{.ref_count = 1}} {} - - /** - * @brief Copy constructor. Takes shared ownership of the callback state, increasing the reference count. - */ - shared_callback(const shared_callback &other) { - this->operator=(other); - } - - /** - * @brief Move constructor. Transfers ownership from another object, leaving intact the reference count. The other object releases the callback state. - */ - shared_callback(shared_callback &&other) noexcept { - this->operator=(std::move(other)); - } - - /** - * @brief Copy assignment. Takes shared ownership of the callback state, increasing the reference count. - */ - shared_callback &operator=(const shared_callback &other) noexcept { - std::lock_guard lock{other.get_mutex()}; - - state = other.state; - ++state->ref_count; - return *this; - } - - /** - * @brief Move assignment. Transfers ownership from another object, leaving intact the reference count. The other object releases the callback state. - */ - shared_callback &operator=(shared_callback &&other) noexcept { - std::lock_guard lock{other.get_mutex()}; - - state = std::exchange(other.state, nullptr); - return *this; - } - - /** - * @brief Function called by the async when it is destroyed when it was never co_awaited, signals to the callback to abort. - */ - void set_dangling() { - if (!state) // moved-from object - return; - std::lock_guard lock{get_mutex()}; - - if (state->state == callback_state::waiting) - state->state = callback_state::dangling; - } - - /** - * @brief Convenience function to get the shared callback state's mutex. - */ - std::mutex &get_mutex() const { - return (state->mutex); - } - - /** - * @brief Convenience function to get the shared callback state's result. - */ - std::optional &get_result() const { - return (state->result); - } - - /** - * @brief Destructor. Releases the held reference and destroys if no other references exist. - */ - ~shared_callback() { - if (!state) // Moved-from object - return; - - std::unique_lock lock{state->mutex}; - - if (state->ref_count) { - --(state->ref_count); - if (state->ref_count <= 0) {; - lock.unlock(); - delete state; - } - } - } - - callback_state *state; - }; - - /** - * @brief Shared state of the async and its callback, to be used across threads. - */ - shared_callback api_callback; - - public: - /** - * @brief Construct an async object wrapping an object method, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. - * - * @param obj The object to call the method on - * @param fun The method of the object to call. Its last parameter must be a callback taking a parameter of type ReturnType - * @param args Parameters to pass to the method, excluding the callback - */ - template -#ifndef _DOXYGEN_ - requires std::invocable> -#endif - async(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} { - std::invoke(std::forward(fun), std::forward(obj), std::forward(args)..., api_callback); - } - - /** - * @brief Construct an async object wrapping an invokeable object, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. - * - * @param fun The object to call using std::invoke. Its last parameter must be a callable taking a parameter of type ReturnType - * @param args Parameters to pass to the object, excluding the callback - */ - template -#ifndef _DOXYGEN_ - requires std::invocable> -#endif - async(Fun &&fun, Args&&... args) : api_callback{} { - std::invoke(std::forward(fun), std::forward(args)..., api_callback); - } - - /** - * @brief Construct an async wrapping an awaitable, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. - * - * @param callable The awaitable object whose API call to execute. - */ - async(const awaitable &awaitable) : api_callback{} { - std::invoke(awaitable.request, api_callback); - } - - /** - * @brief Destructor. If any callback is pending it will be aborted. - * - */ - ~async() { - api_callback.set_dangling(); - } - - /** - * @brief Copy constructor is disabled - */ - async(const async &) = delete; - - /** - * @brief Move constructor - * - * 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. - * - * @remark Using the moved-from async after this function is undefined behavior. - * @param other The async object to move the data from. - */ - async(async &&other) noexcept = default; - - /** - * @brief Copy assignment is disabled - */ - async &operator=(const async &) = delete; - - /** - * @brief Move assignment operator. - * - * 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. - * - * @remark Using the moved-from async after this function is undefined behavior. - * @param other The async object to move the data from - */ - async &operator=(async &&other) noexcept = default; - - /** - * @brief First function called by the standard library when the object is co-awaited. - * - * Returns whether we already have the result of the API call and don't need to suspend the caller. - * - * @remark Do not call this manually, use the co_await keyword instead. - * @return bool Whether we already have the result of the API call or not - */ - bool await_ready() noexcept { - std::lock_guard lock{api_callback.get_mutex()}; - - return api_callback.get_result().has_value(); - } - - /** - * @brief Second function called by the standard library when the object is co-awaited, if await_ready returned false. - * - * Checks again for the presence of the result, if absent, signals to suspend and keep track of the calling coroutine for the callback to resume. - * - * @remark Do not call this manually, use the co_await keyword instead. - * @param handle The handle to the coroutine co_await-ing and being suspended - */ - template - bool await_suspend(detail::std_coroutine::coroutine_handle caller) { - std::lock_guard lock{api_callback.get_mutex()}; - - if (api_callback.get_result().has_value()) - return false; // immediately resume the coroutine as we already have the result of the api call - if constexpr (requires (T t) { t.is_sync = false; }) - caller.promise().is_sync = false; - api_callback.state->coro_handle = caller; - return true; // suspend the caller, the callback will resume it - } - - /** - * @brief Function called by the standard library when the async is resumed. Its return value is what the whole co_await expression evaluates to - * - * @remark Do not call this manually, use the co_await keyword instead. - * @return ReturnType The result of the API call. - */ - ReturnType await_resume() { - // no locking needed here as the callback has already executed - return std::move(*api_callback.get_result()); - } - }; -} // namespace dpp - -/** - * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. - */ -template -struct dpp::detail::std_coroutine::coroutine_traits, Args...> { - using promise_type = dpp::detail::task_promise; -}; - -/** - * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. - */ -template -struct dpp::detail::std_coroutine::coroutine_traits { - /** - * @brief Promise type for this coroutine signature. - * - * When the coroutine is created from a lambda, that lambda is passed as a first parameter. - * Not ideal but we'll allow any callable that takes the rest of the arguments passed - */ - using promise_type = dpp::detail::job_promise<(std::is_reference_v || ... || (std::is_reference_v && !std::is_invocable_v))>; -}; - -#endif +#endif /* DPP_CORO */ diff --git a/include/dpp/coro/async.h b/include/dpp/coro/async.h new file mode 100644 index 0000000000..2137f89dce --- /dev/null +++ b/include/dpp/coro/async.h @@ -0,0 +1,327 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * Copyright 2022 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#ifdef DPP_CORO +#pragma once + +#include "coro.h" + +#include +#include +#include +#include + +namespace dpp { + +struct confirmation_callback_t; + +/** + * @brief A co_await-able object handling an API call in parallel with the caller. + * + * This class is the return type of the dpp::cluster::co_* methods, but it can also be created manually to wrap any async call. + * + * @remark - This object's methods, other than constructors and operators, should not be called directly. It is designed to be used with coroutine keywords such as co_await. + * @remark - This object must not be co_await-ed more than once. + * @remark - The coroutine may be resumed in another thread, do not rely on thread_local variables. + * @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. + * @tparam R The return type of the API call. Defaults to confirmation_callback_t + */ +template +class async { + /** + * @brief Ref-counted callback, contains the callback logic and manages the lifetime of the callback data over multiple threads. + */ + struct shared_callback { + /** + * @brief State of the async and its callback. + */ + struct callback_state { + enum state_t { + waiting, + done, + dangling + }; + + /** + * @brief Mutex to ensure the API result isn't set at the same time the coroutine is awaited and its value is checked, or the async is destroyed + */ + std::mutex mutex{}; + + /** + * @brief Number of references to this callback state. + */ + int ref_count; + + /** + * @brief State of the awaitable and the API callback + */ + state_t state = waiting; + + /** + * @brief The stored result of the API call + */ + std::optional result = std::nullopt; + + /** + * @brief Handle to the coroutine co_await-ing on this API call + * + * @see std::coroutine_handle + */ + detail::std_coroutine::coroutine_handle<> coro_handle = nullptr; + }; + + callback_state *state; + + /** + * @brief Callback function. + * + * @param cback The result of the API call. + */ + void operator()(const R &cback) const { + std::unique_lock lock{get_mutex()}; + + if (state->state == callback_state::dangling) // Async object is gone - likely an exception killed it or it was never co_await-ed + return; + state->result = cback; + state->state = callback_state::done; + if (state->coro_handle) { + auto handle = state->coro_handle; + state->coro_handle = nullptr; + lock.unlock(); + handle.resume(); + } + } + + /** + * @brief Main constructor, allocates a new callback_state object. + */ + shared_callback() : state{new callback_state{.ref_count = 1}} {} + + /** + * @brief Destructor. Releases the held reference and destroys if no other references exist. + */ + ~shared_callback() { + if (!state) // Moved-from object + return; + + std::unique_lock lock{state->mutex}; + + if (state->ref_count) { + --(state->ref_count); + if (state->ref_count <= 0) {; + lock.unlock(); + delete state; + } + } + } + + /** + * @brief Copy constructor. Takes shared ownership of the callback state, increasing the reference count. + */ + shared_callback(const shared_callback &other) { + this->operator=(other); + } + + /** + * @brief Move constructor. Transfers ownership from another object, leaving intact the reference count. The other object releases the callback state. + */ + shared_callback(shared_callback &&other) noexcept { + this->operator=(std::move(other)); + } + + /** + * @brief Copy assignment. Takes shared ownership of the callback state, increasing the reference count. + */ + shared_callback &operator=(const shared_callback &other) noexcept { + std::lock_guard lock{other.get_mutex()}; + + state = other.state; + ++state->ref_count; + return *this; + } + + /** + * @brief Move assignment. Transfers ownership from another object, leaving intact the reference count. The other object releases the callback state. + */ + shared_callback &operator=(shared_callback &&other) noexcept { + std::lock_guard lock{other.get_mutex()}; + + state = std::exchange(other.state, nullptr); + return *this; + } + + /** + * @brief Function called by the async when it is destroyed when it was never co_awaited, signals to the callback to abort. + */ + void set_dangling() { + if (!state) // moved-from object + return; + std::lock_guard lock{get_mutex()}; + + if (state->state == callback_state::waiting) + state->state = callback_state::dangling; + } + + /** + * @brief Convenience function to get the shared callback state's mutex. + */ + std::mutex &get_mutex() const { + return (state->mutex); + } + + /** + * @brief Convenience function to get the shared callback state's result. + */ + std::optional &get_result() const { + return (state->result); + } + }; + + /** + * @brief Shared state of the async and its callback, to be used across threads. + */ + shared_callback api_callback; + +public: + /** + * @brief Construct an async object wrapping an object method, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. + * + * @param obj The object to call the method on + * @param fun The method of the object to call. Its last parameter must be a callback taking a parameter of type R + * @param args Parameters to pass to the method, excluding the callback + */ + template +#ifndef _DOXYGEN_ + requires std::invocable> +#endif + async(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} { + std::invoke(std::forward(fun), std::forward(obj), std::forward(args)..., api_callback); + } + + /** + * @brief Construct an async object wrapping an invokeable object, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. + * + * @param fun The object to call using std::invoke. Its last parameter must be a callable taking a parameter of type R + * @param args Parameters to pass to the object, excluding the callback + */ + template +#ifndef _DOXYGEN_ + requires std::invocable> +#endif + async(Fun &&fun, Args&&... args) : api_callback{} { + std::invoke(std::forward(fun), std::forward(args)..., api_callback); + } + + /** + * @brief Construct an async wrapping an awaitable, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. + * + * @param callable The awaitable object whose API call to execute. + */ + async(const awaitable &awaitable) : api_callback{} { + std::invoke(awaitable.request, api_callback); + } + + /** + * @brief Destructor. If any callback is pending it will be aborted. + */ + ~async() { + api_callback.set_dangling(); + } + + /** + * @brief Copy constructor is disabled + */ + async(const async &) = delete; + + /** + * @brief Move constructor + * + * 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. + * + * @remark Using the moved-from async after this function is undefined behavior. + * @param other The async object to move the data from. + */ + async(async &&other) noexcept = default; + + /** + * @brief Copy assignment is disabled + */ + async &operator=(const async &) = delete; + + /** + * @brief Move assignment operator. + * + * 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. + * + * @remark Using the moved-from async after this function is undefined behavior. + * @param other The async object to move the data from + */ + async &operator=(async &&other) noexcept = default; + + /** + * @brief First function called by the standard library when the object is co-awaited. + * + * Returns whether we already have the result of the API call and don't need to suspend the caller. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @return bool Whether we already have the result of the API call or not + */ + bool await_ready() noexcept { + std::lock_guard lock{api_callback.get_mutex()}; + + return api_callback.get_result().has_value(); + } + + /** + * @brief Second function called by the standard library when the object is co-awaited, if await_ready returned false. + * + * Checks again for the presence of the result, if absent, signals to suspend and keep track of the calling coroutine for the callback to resume. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @param handle The handle to the coroutine co_await-ing and being suspended + */ + template + bool await_suspend(detail::std_coroutine::coroutine_handle caller) { + std::lock_guard lock{api_callback.get_mutex()}; + + if (api_callback.get_result().has_value()) + return false; // immediately resume the coroutine as we already have the result of the api call + if constexpr (requires (T t) { t.is_sync = false; }) + caller.promise().is_sync = false; + api_callback.state->coro_handle = caller; + return true; // suspend the caller, the callback will resume it + } + + /** + * @brief Function called by the standard library when the async is resumed. Its return value is what the whole co_await expression evaluates to + * + * @remark Do not call this manually, use the co_await keyword instead. + * @return R The result of the API call. + */ + R await_resume() { + // no locking needed here as the callback has already executed + return std::move(*api_callback.get_result()); + } +}; + +} // namespace dpp + +#endif /* DPP_CORO */ diff --git a/include/dpp/coro/coro.h b/include/dpp/coro/coro.h new file mode 100644 index 0000000000..80ecd485a5 --- /dev/null +++ b/include/dpp/coro/coro.h @@ -0,0 +1,86 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * Copyright 2022 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#ifdef DPP_CORO +#pragma once + +#if (defined(_LIBCPP_VERSION) and !defined(__cpp_impl_coroutine)) // if libc++ experimental implementation (LLVM < 14) +# define STDCORO_EXPERIMENTAL_HEADER +# define STDCORO_EXPERIMENTAL_NAMESPACE +#endif + +#ifdef STDCORO_GLIBCXX_COMPAT +# define __cpp_impl_coroutine 1 +namespace std { + namespace experimental { + using namespace std; + } +} +#endif + +#ifdef STDCORO_EXPERIMENTAL_HEADER +# include +#else +# include +#endif + +namespace dpp { + +/** + * @brief Implementation details for internal use only. + * + * @attention This is only meant to be used by D++ internally. Support will not be given regarding the facilities in this namespace. + */ +namespace detail { +#ifdef _DOXYGEN_ +/** + * @brief Alias for either std or std::experimental depending on compiler and library. Used by coroutine implementation. + * + * @todo Remove and use std when all supported libraries have coroutines in it + */ +namespace std_coroutine {} +#else +# ifdef STDCORO_EXPERIMENTAL_NAMESPACE +namespace std_coroutine = std::experimental; +# else +namespace std_coroutine = std; +# endif +#endif + +} // namespace detail + +struct confirmation_callback_t; + +template +class async; + +template +#ifndef _DOXYGEN_ +requires std::is_same_v || (!std::is_reference_v && std::is_move_constructible_v && std::is_move_assignable_v) +#endif +class task; + +struct job; + +} // namespace dpp + +#endif /* DPP_CORO */ + diff --git a/include/dpp/coro/job.h b/include/dpp/coro/job.h new file mode 100644 index 0000000000..992063781f --- /dev/null +++ b/include/dpp/coro/job.h @@ -0,0 +1,129 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * Copyright 2022 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#ifdef DPP_CORO +#pragma once + +#include "coro.h" + +#include +#include + +namespace dpp { + +/** + * @brief Extremely light coroutine object designed to send off a coroutine to execute on its own. + * + * This object is extremely light, and is the recommended way to use coroutines if you do not need to co_await the result. + * + * @warning It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing. + * At this point, if the function returns, every object declared in the function including its parameters are destroyed, which causes dangling references. + * This is exactly the same problem as references in lambdas : https://dpp.dev/lambdas-and-locals.html. + * For this reason, `co_await` will error if any parameters are passed by reference. + * If you must pass a reference, pass it as a pointer or with std::ref, but you must fully understand the reason behind this warning, and what to avoid. + * If you prefer a safer type, use `coroutine` for synchronous execution, or `task` for parallel tasks, and co_await them. + */ +struct job {}; + +namespace detail { + +/** + * @brief Coroutine promise type for a job + */ +template +struct job_promise { + /* + * @brief Function called when the job is done. + * + * @return Do not suspend at the end, destroying the handle immediately + */ + std_coroutine::suspend_never final_suspend() const noexcept { + return {}; + } + + /* + * @brief Function called when the job is started. + * + * @return Do not suspend at the start, starting the job immediately + */ + std_coroutine::suspend_never initial_suspend() const noexcept { + return {}; + } + + /** + * @brief Function called to get the job object + * + * @return job + */ + dpp::job get_return_object() const noexcept { + return {}; + } + + /** + * @brief Function called when an exception is thrown and not caught. + * + * @throw Immediately rethrows the exception to the caller / resumer + */ + void unhandled_exception() const noexcept(false) { + throw; + } + + /** + * @brief Function called when the job returns. Does nothing. + */ + void return_void() const noexcept {} + + template + T await_transform(T &&expr) const noexcept { + /** + * `job` is extremely efficient as a coroutine but this comes with drawbacks : + * It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing. + * At this point, if the function returns, every object declared in the function including its parameters are destroyed, which causes dangling references. + * This is exactly the same problem as references in lambdas : https://dpp.dev/lambdas-and-locals.html. + * + * If you must pass a reference, pass it as a pointer or with std::ref, but you must fully understand the reason behind this warning, and what to avoid. + * If you prefer a safer type, use `coroutine` for synchronous execution, or `task` for parallel tasks, and co_await them. + */ + static_assert(!has_reference_params, "co_await is disabled in dpp::job when taking parameters by reference. read comment above this line for more info"); + + return std::forward(expr); + } +}; + +} // namespace detail + +} // namespace dpp + +/** + * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. + */ +template +struct dpp::detail::std_coroutine::coroutine_traits { + /** + * @brief Promise type for this coroutine signature. + * + * When the coroutine is created from a lambda, that lambda is passed as a first parameter. + * Not ideal but we'll allow any callable that takes the rest of the arguments passed + */ + using promise_type = dpp::detail::job_promise<(std::is_reference_v || ... || (std::is_reference_v && !std::is_invocable_v))>; +}; + +#endif /* DPP_CORO */ diff --git a/include/dpp/coro/task.h b/include/dpp/coro/task.h new file mode 100644 index 0000000000..703ff031e0 --- /dev/null +++ b/include/dpp/coro/task.h @@ -0,0 +1,418 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * Copyright 2022 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#ifdef DPP_CORO +#pragma once + +#include "coro.h" + +#include +#include +#include +#include +#include +#include + +namespace dpp { + +namespace detail { + +/** + * @brief A task's promise type, with special logic for handling nested tasks. + */ +template +struct task_promise; + +/** + * @brief The object automatically co_await-ed at the end of a task. Ensures nested task chains are resolved, and the promise cleans up if it needs to. + */ +template +struct task_chain_final_awaiter; + +/** + * @brief Alias for std::coroutine_handle for a task_promise. + */ +template +using task_handle = detail::std_coroutine::coroutine_handle>; + +} // namespace detail + +/** + * @brief A coroutine task. It can be co_awaited to make nested coroutines. + * + * Can be used in conjunction with coroutine events via dpp::event_router_t::co_attach, or on its own. + * + * @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. + * @tparam R Return type of the coroutine. Can be void, or a complete object that supports move construction and move assignment. + */ +template +#ifndef _DOXYGEN_ +requires std::is_same_v || (!std::is_reference_v && std::is_move_constructible_v && std::is_move_assignable_v) +#endif +class task { + /** + * @brief The coroutine handle of this task. + */ + detail::task_handle handle; + + /** + * @brief Promise type of this coroutine. For internal use only, do not use. + */ + friend struct detail::task_promise; + + /** + * @brief Construct from a coroutine handle. Internal use only + */ + explicit task(detail::task_handle handle_) : handle(handle_) {} + +public: + /** + * @brief Default constructor, creates a task not bound to a coroutine. + */ + task() = default; + + /** + * @brief Copy constructor is disabled + */ + task(const task &) = delete; + + /** + * @brief Move constructor, grabs another task's coroutine handle + * + * @param other Task to move the handle from + */ + task(task &&other) noexcept : handle(std::exchange(other.handle, nullptr)) {} + + + /** + * @brief Destructor. + * + * Destroys the handle if coroutine is done, otherwise detaches it from this thread. + * In detached mode, the handle will destroy itself at the end of the coroutine. + */ + ~task() { + if (handle) { + auto &promise = handle.promise(); + + if (!promise.is_sync) { + std::unique_lock lock{promise.mutex}; + + if (promise.destroy) // promise in async thread checked first and skipped clean up, we do it + { + if (promise.exception && promise.exception_handler) + promise.exception_handler(promise.exception); + lock.unlock(); + handle.destroy(); + } + else + handle.promise().destroy = true; + } + else { + if (promise.exception && promise.exception_handler) + promise.exception_handler(promise.exception); + handle.destroy(); + } + } + } + + /** + * @brief Copy assignment is disabled + */ + task &operator=(const task &) = delete; + + /** + * @brief Move assignment, grabs another task's coroutine handle + * + * @param other Task to move the handle from + */ + task &operator=(task &&other) noexcept { + handle = std::exchange(other.handle, nullptr); + return (*this); + } + + /** + * @brief First function called by the standard library when the task is co_await-ed. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @return bool Whether not to suspend the caller or not + */ + bool await_ready() { + return handle.promise().is_sync; + } + + /** + * @brief Second function called by the standard library when the task is co_await-ed, if await_ready returned false. + * + * Stores the calling coroutine in the promise to resume when this task suspends. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @param caller The calling coroutine, now suspended + * @return bool Whether to suspend the caller or not + */ + template + bool await_suspend(detail::std_coroutine::coroutine_handle caller) { + auto &my_promise = handle.promise(); + + if (my_promise.is_sync) + return false; + + std::lock_guard lock{my_promise.mutex}; + + if (handle.done()) + return (false); + if constexpr (requires (T t) { t.is_sync = false; }) + caller.promise().is_sync = false; + my_promise.parent = caller; + return true; + } + + /** + * @brief Function called by the standard library when the coroutine is resumed. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @throw Throws any exception thrown or uncaught by the coroutine + * @return R The result of the coroutine. It is the value the whole co-await expression evaluates to + */ + R await_resume(); + + /** + * @brief Function to check if the coroutine has finished its execution entirely + * + * @return bool Whether the coroutine is done. + * @see https://en.cppreference.com/w/cpp/coroutine/coroutine_handle/done + */ + bool done() const noexcept { + return handle.done(); + } + + /** + * @brief Set the exception handling function. Called when an exception is thrown but not caught + * + * @warning The exception handler must not throw. If an exception that is not caught is thrown in a detached task, the program will terminate. + */ + task &on_exception(std::function func) { + handle.promise().exception_handler = std::move(func); + if (handle.promise().exception) + func(handle.promise().exception); + return *this; + } +}; + +namespace detail { + /** + * @brief Awaitable returned from task_promise's final_suspend. Resumes the parent and cleans up its handle if needed + */ +template +struct task_chain_final_awaiter { + /** + * @brief Always suspend at the end of the task. This allows us to clean up and resume the parent + */ + bool await_ready() noexcept { + return (false); + } + + /* + * @brief The suspension logic of the coroutine when it finishes. Always suspend the caller, meaning cleaning up the handle is on us + * + * @param handle The handle of this coroutine + */ + void await_suspend(detail::task_handle handle) noexcept; + + /* + * @brief Function called when this object is co_awaited by the standard library at the end of final_suspend. Do nothing, return nothing + */ + void await_resume() noexcept {} +}; +/** + * @brief Base implementation of task_promise, without the logic that would depend on the return type. Meant to be inherited from + */ +struct task_promise_base { + /** + * @brief Mutex for async task destruction. + */ + std::mutex mutex{}; + + /** + * @brief Parent coroutine to return to for nested coroutines. + */ + detail::std_coroutine::coroutine_handle<> parent = nullptr; + + /** + * @brief Exception ptr if any was thrown during the coroutine + * + * @see std::exception_ptr + */ + std::exception_ptr exception = nullptr; + + /** + * @brief Whether the coroutine has async calls or not + * + * Will only ever change on the calling thread while callback mutex guards the async thread + */ + bool is_sync = true; + + /** + * @brief Whether either the task object or the promise is gone and the next one to end will clean up + */ + bool destroy = false; + + /** + * @brief Function object called when an exception is thrown from a coroutine + */ + std::function exception_handler = nullptr; + + /** + * @brief Function called by the standard library when the coroutine is created. + * + * @return std::suspend_never Don't suspend, the coroutine starts immediately. + */ + std_coroutine::suspend_never initial_suspend() noexcept { + return {}; + } + + /** + * @brief Function called by the standard library when an exception is thrown and not caught in the coroutine. + * + * Stores the exception pointer to rethrow later + */ + void unhandled_exception() { + exception = std::current_exception(); + } +}; + +/** + * @brief Implementation of task_promise for non-void return type + */ +template +struct task_promise : task_promise_base { + /** + * @brief Stored return value of the coroutine. + * + * @details The main reason we use std::optional here and not R is to avoid default construction of the value so we only require R to have a move constructor, instead of both a default constructor and move assignment operator + */ + std::optional value = std::nullopt; + + /** + * @brief Function called by the standard library when the coroutine co_returns a value. + * + * Stores the value internally to hand to the caller when it resumes. + * + * @param expr The value given to co_return + */ + void return_value(R expr) { + value = std::move(expr); + } + + /** + * @brief Function called by the standard library when the coroutine is created. + * + * @return task The coroutine object + */ + task get_return_object() { + return task{task_handle::from_promise(*this)}; + } + + /** + * @brief Function called by the standard library when the coroutine reaches its last suspension point + * + * @return task_chain_final_awaiter Special object containing the chain resolution and clean-up logic. + */ + task_chain_final_awaiter final_suspend() noexcept { + return {}; + } +}; + +/** + * @brief Implementation of task_promise for void return type + */ +template <> +struct task_promise : task_promise_base { + /** + * @brief Function called by the standard library when the coroutine co_returns + * + * Does nothing but is required by the standard library. + */ + void return_void() {} + + /** + * @brief Function called by the standard library when the coroutine is created. + * + * @return task The coroutine object + */ + task get_return_object() { + return task{task_handle::from_promise(*this)}; + } + + /** + * @brief Function called by the standard library when the coroutine reaches its last suspension point + * + * @return task_chain_final_awaiter Special object containing the chain resolution and clean-up logic. + */ + task_chain_final_awaiter final_suspend() noexcept { + return {}; + } +}; + +template +void detail::task_chain_final_awaiter::await_suspend(detail::task_handle handle) noexcept { + task_promise &promise = handle.promise(); + std_coroutine::coroutine_handle<> parent = promise.parent; + + if (!promise.is_sync) { + std::unique_lock lock{promise.mutex}; + + if (promise.destroy) { + if (promise.exception && promise.exception_handler) + promise.exception_handler(promise.exception); + lock.unlock(); + handle.destroy(); + } + else + promise.destroy = true; // Send the destruction back to the task + } + if (parent) + parent.resume(); +} + +} // namespace detail + +template +#ifndef _DOXYGEN_ +requires std::is_same_v || (!std::is_reference_v && std::is_move_constructible_v && std::is_move_assignable_v) +#endif +R task::await_resume() { + if (handle.promise().exception) // If we have an exception, rethrow + std::rethrow_exception(handle.promise().exception); + if constexpr (!std::is_same_v) // If we have a return type, return it and clean up our stored value + return *std::exchange(handle.promise().value, std::nullopt); +} + +} // namespace dpp + +/** + * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. + */ +template +struct dpp::detail::std_coroutine::coroutine_traits, Args...> { + using promise_type = dpp::detail::task_promise; +}; + +#endif /* DPP_CORO */ diff --git a/include/dpp/event_router.h b/include/dpp/event_router.h index db208dfc21..e20e6625ae 100644 --- a/include/dpp/event_router.h +++ b/include/dpp/event_router.h @@ -32,7 +32,7 @@ #include #include #include -#include +#include using json = nlohmann::json; @@ -242,7 +242,6 @@ template class event_router_t { * detach the listener from the event later if necessary. */ event_handle co_attach(std::function func) { - // ^ If this errors here - your event handler must take its parameter by VALUE std::unique_lock l(lock); event_handle h = next_handle++; coroutine_container.emplace(h, func); From cb6a103a90e4af594b5c516e8f47bb7510634e60 Mon Sep 17 00:00:00 2001 From: Amber Ehrlich Date: Thu, 10 Aug 2023 19:24:57 -0400 Subject: [PATCH 11/21] feat(coro): coroutine, a lightweight synchronized coroutine object --- include/dpp/coro.h | 1 + include/dpp/coro/coro.h | 3 + include/dpp/coro/coroutine.h | 511 +++++++++++++++++++++++++++++++++++ include/dpp/coro/job.h | 6 +- 4 files changed, 518 insertions(+), 3 deletions(-) create mode 100644 include/dpp/coro/coroutine.h diff --git a/include/dpp/coro.h b/include/dpp/coro.h index bfd3ad6120..df83e58a6c 100644 --- a/include/dpp/coro.h +++ b/include/dpp/coro.h @@ -23,6 +23,7 @@ #pragma once #include "coro/async.h" +#include "coro/coroutine.h" #include "coro/job.h" #include "coro/task.h" diff --git a/include/dpp/coro/coro.h b/include/dpp/coro/coro.h index 80ecd485a5..656bebcfae 100644 --- a/include/dpp/coro/coro.h +++ b/include/dpp/coro/coro.h @@ -78,6 +78,9 @@ requires std::is_same_v || (!std::is_reference_v && std::is_move_con #endif class task; +template +class coroutine; + struct job; } // namespace dpp diff --git a/include/dpp/coro/coroutine.h b/include/dpp/coro/coroutine.h new file mode 100644 index 0000000000..5cb20905f6 --- /dev/null +++ b/include/dpp/coro/coroutine.h @@ -0,0 +1,511 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * Copyright 2022 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#ifdef DPP_CORO +#pragma once + +#include "coro.h" + +#include +#include +#include +#include +#include + +namespace dpp { + +namespace detail { + + template +struct coroutine_promise; + +template +/** + * @brief Alias for the coroutine_handle of a coroutine. + */ +using coroutine_handle = std_coroutine::coroutine_handle>; + +/** + * @brief Base class of dpp::coroutine. This class should not be used directly by a user, use dpp::coroutine instead. + * + * @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::coroutine so a user cannot call await_suspend and await_resume directly. + */ +template +class coroutine_base { +protected: + /** + * @brief Promise has friend access for the constructor + */ + friend struct detail::coroutine_promise; + + /** + * @brief Coroutine handle. + */ + detail::coroutine_handle handle{nullptr}; + +private: + /** + * @brief Construct from a handle. Internal use only. + */ + coroutine_base(detail::coroutine_handle h) : handle{h} {} + +public: + /** + * @brief Default constructor, creates an empty coroutine. + */ + coroutine_base() = default; + + /** + * @brief Copy constructor is disabled + */ + coroutine_base(const coroutine_base &) = delete; + + /** + * @brief Move constructor, grabs another coroutine's handle + * + * @param other Coroutine to move the handle from + */ + coroutine_base(coroutine_base &&other) noexcept : handle(std::exchange(other.handle, nullptr)) {} + + /** + * @brief Destructor, destroys the handle. + */ + ~coroutine_base() { + if (handle) + handle.destroy(); + } + + /** + * @brief Copy assignment is disabled + */ + coroutine_base &operator=(const coroutine_base &) = delete; + + /** + * @brief Move assignment, grabs another coroutine's handle + * + * @param other Coroutine to move the handle from + */ + coroutine_base &operator=(coroutine_base &&other) noexcept { + handle = std::exchange(other.handle, nullptr); + return *this; + } + + /** + * @brief First function called by the standard library when the coroutine is co_await-ed. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @throws invalid_operation_exception if the coroutine is empty or finished. + * @return bool Whether the coroutine is done + */ + bool await_ready() const { + if (!handle) + throw dpp::logic_exception("cannot co_await an empty coroutine"); + return handle.done(); + } + + /** + * @brief Second function called by the standard library when the coroutine is co_await-ed. + * + * Stores the calling coroutine in the promise to resume when this coroutine suspends. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @param caller The calling coroutine, now suspended + */ + template + detail::coroutine_handle await_suspend(detail::std_coroutine::coroutine_handle caller) noexcept { + handle.promise().parent = caller; + return handle; + } + + /** + * @brief Function called by the standard library when the coroutine is resumed. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @throw Throws any exception thrown or uncaught by the coroutine + * @return R The result of the coroutine. It is given to the caller as a result to `co_await` + */ + decltype(auto) await_resume() & { + return static_cast &>(*this).await_resume_impl(); + } + + /** + * @brief Function called by the standard library when the coroutine is resumed. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @throw Throws any exception thrown or uncaught by the coroutine + * @return R The result of the coroutine. It is given to the caller as a result to `co_await` + */ + decltype(auto) await_resume() const & { + return static_cast const&>(*this).await_resume_impl(); + } + + /** + * @brief Function called by the standard library when the coroutine is resumed. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @throw Throws any exception thrown or uncaught by the coroutine + * @return R The result of the coroutine. It is given to the caller as a result to `co_await` + */ + decltype(auto) await_resume() && { + return static_cast &&>(*this).await_resume_impl(); + } +}; + +} // namespace detail + +/** + * @brief Base type for a coroutine, starts on co_await. + * + * @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. + * @warning - Using co_await on this object more than once is undefined behavior. + * @tparam R Return type of the coroutine. Can be void, or a complete object that supports move construction and move assignment. + */ +template +class coroutine : private detail::coroutine_base { + /** + * @brief Base class has friend access for CRTP downcast + */ + friend class detail::coroutine_base; + + R& await_resume_impl() & { + detail::coroutine_promise &promise = this->handle.promise(); + if (promise.exception) + std::rethrow_exception(promise.exception); + return *promise.result; + } + + const R& await_resume_impl() const & { + detail::coroutine_promise &promise = this->handle.promise(); + if (promise.exception) + std::rethrow_exception(promise.exception); + return *promise.result; + } + + R&& await_resume_impl() && { + detail::coroutine_promise &promise = this->handle.promise(); + if (promise.exception) + std::rethrow_exception(promise.exception); + return *std::move(promise.result); + } + +public: + using detail::coroutine_base::coroutine_base; // use coroutine_base's constructors + using detail::coroutine_base::operator=; // use coroutine_base's assignment operators + using detail::coroutine_base::await_ready; // expose await_ready as public + + /** + * @brief Suspend the caller until the coroutine completes. + * + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. + * @return R& On resumption, this expression evaluates to the result object of type R, as a reference. + */ + auto& operator co_await() & noexcept { + return static_cast&>(*this); + } + + /** + * @brief Suspend the caller until the coroutine completes. + * + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. + * @return const R& On resumption, this expression evaluates to the result object of type R, as a const reference. + */ + const auto& operator co_await() const & noexcept { + return static_cast const&>(*this); + } + + /** + * @brief Suspend the caller until the coroutine completes. + * + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. + * @return R&& On resumption, this expression evaluates to the result object of type R, as an rvalue reference. + */ + auto&& operator co_await() && noexcept { + return static_cast&&>(*this); + } +}; + +/** + * @brief Base type for a coroutine, starts on co_await. + * + * @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. + * @warning - Using co_await on this object more than once is undefined behavior. + * @tparam R Return type of the coroutine. Can be void, or a complete object that supports move construction and move assignment. + */ +template <> +class coroutine : private detail::coroutine_base { + /** + * @brief Base class has friend access for CRTP downcast + */ + friend class detail::coroutine_base; + + void await_resume_impl() const; + +public: + using detail::coroutine_base::coroutine_base; // use coroutine_base's constructors + using detail::coroutine_base::operator=; // use coroutine_base's assignment operators + using detail::coroutine_base::await_ready; // expose await_ready as public + + /** + * @brief Suspend the current coroutine until the coroutine completes. + * + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. + * @return R& On resumption, this expression evaluates to the result object of type R, as a reference. + */ + auto& operator co_await() & noexcept { + return static_cast&>(*this); + } + + /** + * @brief Suspend the current coroutine until the coroutine completes. + * + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. + * @return const R& On resumption, this expression evaluates to the result object of type R, as a const reference. + */ + const auto& operator co_await() const & noexcept { + return static_cast const &>(*this); + } + + /** + * @brief Suspend the current coroutine until the coroutine completes. + * + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. + * @return R&& On resumption, this expression evaluates to the result object of type R, as an rvalue reference. + */ + auto&& operator co_await() && noexcept { + return static_cast&&>(*this); + } +}; + +namespace detail { + template + struct coroutine_final_awaiter; + +#ifdef DPP_CORO_TEST + struct coroutine_promise_base{}; +#endif + + /** + * @brief Promise type for coroutine. + */ + template + struct coroutine_promise { + /** + * @brief Handle of the coroutine co_await-ing this coroutine. + */ + std_coroutine::coroutine_handle<> parent{nullptr}; + + /** + * @brief Return value of the coroutine + */ + std::optional result{}; + + /** + * @brief Pointer to an uncaught exception thrown by the coroutine + */ + std::exception_ptr exception{nullptr}; + +#ifdef DPP_CORO_TEST + coroutine_promise() { + ++coro_alloc_count; + } + + ~coroutine_promise() { + --coro_alloc_count; + } +#endif + + /** + * @brief Function called by the standard library when reaching the end of a coroutine + * + * @return coroutine_final_awaiter Resumes any coroutine co_await-ing on this + */ + coroutine_final_awaiter final_suspend() const noexcept; + + /** + * @brief Function called by the standard library when the coroutine start + * + * @return suspend_always Always suspend at the start, for a lazy start + */ + std_coroutine::suspend_always initial_suspend() const noexcept { + return {}; + } + + /** + * @brief Function called when an exception escapes the coroutine + * + * Stores the exception to throw to the co_await-er + */ + void unhandled_exception() noexcept { + exception = std::current_exception(); + } + + /** + * @brief Function called by the standard library when the coroutine co_returns a value. + * + * Stores the value internally to hand to the caller when it resumes. + * + * @param expr The value given to co_return + */ + void return_value(R&& expr) noexcept(std::is_nothrow_move_constructible_v) requires std::move_constructible { + result = static_cast(expr); + } + + /** + * @brief Function called by the standard library when the coroutine co_returns a value. + * + * Stores the value internally to hand to the caller when it resumes. + * + * @param expr The value given to co_return + */ + void return_value(const R &expr) noexcept(std::is_nothrow_copy_constructible_v) requires std::copy_constructible { + result = expr; + } + + /** + * @brief Function called by the standard library when the coroutine co_returns a value. + * + * Stores the value internally to hand to the caller when it resumes. + * + * @param expr The value given to co_return + */ + template + requires (!std::is_same_v> && std::convertible_to) + void return_value(T&& expr) noexcept (std::is_nothrow_convertible_v) { + result = std::forward(expr); + } + + /** + * @brief Function called to get the coroutine object + */ + coroutine get_return_object() { + return coroutine{coroutine_handle::from_promise(*this)}; + } + }; + + /** + * @brief Struct returned by a coroutine's final_suspend, resumes the continuation + */ + template + struct coroutine_final_awaiter { + /** + * @brief First function called by the standard library when reaching the end of a coroutine + * + * @return false Always return false, we need to suspend to resume the parent + */ + bool await_ready() const noexcept { + return false; + } + + /** + * @brief Second function called by the standard library when reaching the end of a coroutine. + * + * @return std::coroutine_handle<> Coroutine handle to resume, this is either the parent if present or std::noop_coroutine() + */ + std_coroutine::coroutine_handle<> await_suspend(std_coroutine::coroutine_handle> handle) const noexcept { + auto parent = handle.promise().parent; + + return parent ? parent : std_coroutine::noop_coroutine(); + } + + /** + * @brief Function called by the standard library when this object is resumed + */ + void await_resume() const noexcept {} + }; + + template + coroutine_final_awaiter coroutine_promise::final_suspend() const noexcept { + return {}; + } + + /** + * @brief Struct returned by a coroutine's final_suspend, resumes the continuation + */ + template <> + struct coroutine_promise { + /** + * @brief Handle of the coroutine co_await-ing this coroutine. + */ + std_coroutine::coroutine_handle<> parent{nullptr}; + + /** + * @brief Pointer to an uncaught exception thrown by the coroutine + */ + std::exception_ptr exception{nullptr}; + + /** + * @brief Function called by the standard library when reaching the end of a coroutine + * + * @return coroutine_final_awaiter Resumes any coroutine co_await-ing on this + */ + coroutine_final_awaiter final_suspend() const noexcept { + return {}; + } + + /** + * @brief Function called by the standard library when the coroutine start + * + * @return suspend_always Always suspend at the start, for a lazy start + */ + std_coroutine::suspend_always initial_suspend() const noexcept { + return {}; + } + + /** + * @brief Function called when an exception escapes the coroutine + * + * Stores the exception to throw to the co_await-er + */ + void unhandled_exception() noexcept { + exception = std::current_exception(); + } + + /** + * @brief Function called when co_return is used + */ + void return_void() const noexcept {} + + /** + * @brief Function called to get the coroutine object + */ + coroutine get_return_object() { + return coroutine{coroutine_handle::from_promise(*this)}; + } + }; + +} // namespace detail + +inline void coroutine::await_resume_impl() const { + if (handle.promise().exception) + std::rethrow_exception(handle.promise().exception); +} + +} // namespace dpp + +/** + * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. + */ +template +struct dpp::detail::std_coroutine::coroutine_traits, Args...> { + using promise_type = dpp::detail::coroutine_promise; +}; + +#endif /* DPP_CORO */ diff --git a/include/dpp/coro/job.h b/include/dpp/coro/job.h index 992063781f..ff3e933ad4 100644 --- a/include/dpp/coro/job.h +++ b/include/dpp/coro/job.h @@ -30,9 +30,9 @@ namespace dpp { /** - * @brief Extremely light coroutine object designed to send off a coroutine to execute on its own. + * @brief Extremely light coroutine object designed to send off a coroutine to execute on its own. It can be attached to an event router using dpp::event_router_t::co_attach. * - * This object is extremely light, and is the recommended way to use coroutines if you do not need to co_await the result. + * This object stores no state and is the recommended way to use coroutines if you do not need to co_await the result. * * @warning It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing. * At this point, if the function returns, every object declared in the function including its parameters are destroyed, which causes dangling references. @@ -82,7 +82,7 @@ struct job_promise { * * @throw Immediately rethrows the exception to the caller / resumer */ - void unhandled_exception() const noexcept(false) { + void unhandled_exception() const { throw; } From 1b334993062096c8e466a593600c45d6f9ef9617 Mon Sep 17 00:00:00 2001 From: Amber Ehrlich Date: Fri, 18 Aug 2023 17:38:01 -0400 Subject: [PATCH 12/21] perf(coro): lock free dpp::async --- .../classes/Generator/CoroGenerator.php | 12 +- include/dpp/cluster.h | 8 +- include/dpp/cluster_coro_calls.h | 378 ++++----- include/dpp/coro/async.h | 352 +++++--- include/dpp/dispatcher.h | 24 +- src/dpp/cluster/timer.cpp | 6 +- src/dpp/cluster_coro_calls.cpp | 756 +++++++++--------- src/dpp/dispatcher.cpp | 48 +- 8 files changed, 848 insertions(+), 736 deletions(-) diff --git a/buildtools/classes/Generator/CoroGenerator.php b/buildtools/classes/Generator/CoroGenerator.php index 1ed3604dc2..70cb5126a8 100644 --- a/buildtools/classes/Generator/CoroGenerator.php +++ b/buildtools/classes/Generator/CoroGenerator.php @@ -90,7 +90,7 @@ public function checkForChanges(): bool */ public function generateHeaderDef(string $returnType, string $currentFunction, string $parameters, string $noDefaults, string $parameterTypes, string $parameterNames): string { - return "awaitable co_{$currentFunction}($parameters);\n\n"; + return "[[nodiscard]] async co_{$currentFunction}($parameters);\n\n"; } /** @@ -98,9 +98,9 @@ public function generateHeaderDef(string $returnType, string $currentFunction, s */ public function generateCppDef(string $returnType, string $currentFunction, string $parameters, string $noDefaults, string $parameterTypes, string $parameterNames): string { - if (substr($parameterNames, 0, 2) === ", ") - $parameterNames = substr($parameterNames, 2); - return "awaitable cluster::co_${currentFunction}($noDefaults) {\n\treturn [=, this] (auto &&cc) { this->$currentFunction($parameterNames" . (empty($parameterNames) ? "": ", ") . "cc); };\n}\n\n"; + /* if (substr($parameterNames, 0, 2) === ", ") + $parameterNames = substr($parameterNames, 2); */ + return "async cluster::co_${currentFunction}($noDefaults) {\n\treturn async{ this, static_cast(&cluster::$currentFunction)$parameterNames };\n}\n\n"; } /** @@ -116,7 +116,7 @@ public function getCommentArray(): array */ public function saveHeader(string $content): void { - $content .= "awaitable co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", std::multimap headers = {});\n\n"; + $content .= "[[nodiscard]] async co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", const std::multimap &headers = {});\n\n"; file_put_contents('include/dpp/cluster_coro_calls.h', $content); } @@ -125,7 +125,7 @@ public function saveHeader(string $content): void */ public function saveCpp(string $cppcontent): void { - $cppcontent .= "dpp::awaitable dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, std::multimap headers) {\n\treturn awaitable{[=, this, h = std::move(headers)](auto &&cc) { this->request(url, method, cc, postdata, mimetype, h); }};\n} + $cppcontent .= "dpp::async dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers) {\n\treturn async{ [&, this] (C &&cc) { return this->request(url, method, std::forward(cc), postdata, mimetype, headers); }};\n} #endif "; diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 96303e71be..4bcea6d2ad 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -39,7 +39,7 @@ #include #include #include -#include +#include #include #include #include @@ -47,7 +47,7 @@ #include #include #include -#include +#include namespace dpp { @@ -362,9 +362,9 @@ class DPP_EXPORT cluster { * @brief Get an awaitable to wait a certain amount of seconds. Use the co_await keyword on its return value to suspend the coroutine until the timer ends * * @param seconds How long to wait for - * @return awaitable Object that can be co_await-ed to suspend the function for a certain time + * @return async Object that can be co_await-ed to suspend the function for a certain time */ - awaitable co_sleep(uint64_t seconds); + [[nodiscard]] async co_sleep(uint64_t seconds); #endif /** diff --git a/include/dpp/cluster_coro_calls.h b/include/dpp/cluster_coro_calls.h index 30623d42e8..d6916ef66a 100644 --- a/include/dpp/cluster_coro_calls.h +++ b/include/dpp/cluster_coro_calls.h @@ -38,7 +38,7 @@ * @return slashcommand_map returned object on completion * \memberof dpp::cluster */ -awaitable co_global_bulk_command_create(const std::vector &commands); +[[nodiscard]] async co_global_bulk_command_create(const std::vector &commands); /** * @brief Create a global slash command (a bot can have a maximum of 100 of these). @@ -49,7 +49,7 @@ awaitable co_global_bulk_command_create(const std::vect * @return slashcommand returned object on completion * \memberof dpp::cluster */ -awaitable co_global_command_create(const slashcommand &s); +[[nodiscard]] async co_global_command_create(const slashcommand &s); /** * @brief Get a global slash command @@ -60,7 +60,7 @@ awaitable co_global_command_create(const slashcommand & * @return slashcommand returned object on completion * \memberof dpp::cluster */ -awaitable co_global_command_get(snowflake id); +[[nodiscard]] async co_global_command_get(snowflake id); /** * @brief Delete a global slash command (a bot can have a maximum of 100 of these) @@ -71,7 +71,7 @@ awaitable co_global_command_get(snowflake id); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_global_command_delete(snowflake id); +[[nodiscard]] async co_global_command_delete(snowflake id); /** * @brief Edit a global slash command (a bot can have a maximum of 100 of these) @@ -82,7 +82,7 @@ awaitable co_global_command_delete(snowflake id); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_global_command_edit(const slashcommand &s); +[[nodiscard]] async co_global_command_edit(const slashcommand &s); /** * @brief Get the application's global slash commands @@ -92,7 +92,7 @@ awaitable co_global_command_edit(const slashcommand &s) * @return slashcommand_map returned object on completion * \memberof dpp::cluster */ -awaitable co_global_commands_get(); +[[nodiscard]] async co_global_commands_get(); /** * @brief Create/overwrite guild slash commands. @@ -106,7 +106,7 @@ awaitable co_global_commands_get(); * @return slashcommand_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_bulk_command_create(const std::vector &commands, snowflake guild_id); +[[nodiscard]] async co_guild_bulk_command_create(const std::vector &commands, snowflake guild_id); /** * @brief Get all slash command permissions of a guild @@ -117,7 +117,7 @@ awaitable co_guild_bulk_command_create(const std::vecto * @return guild_command_permissions_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_commands_get_permissions(snowflake guild_id); +[[nodiscard]] async co_guild_commands_get_permissions(snowflake guild_id); /** * @brief Edit/Overwrite the permissions of all existing slash commands in a guild @@ -133,7 +133,7 @@ awaitable co_guild_commands_get_permissions(snowflake g * @deprecated This has been disabled with updates to Permissions v2. You can use guild_command_edit_permissions instead * \memberof dpp::cluster */ -awaitable co_guild_bulk_command_edit_permissions(const std::vector &commands, snowflake guild_id); +[[nodiscard]] async co_guild_bulk_command_edit_permissions(const std::vector &commands, snowflake guild_id); /** * @brief Create a slash command local to a guild @@ -146,7 +146,7 @@ awaitable co_guild_bulk_command_edit_permissions(const * @return slashcommand returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_command_create(const slashcommand &s, snowflake guild_id); +[[nodiscard]] async co_guild_command_create(const slashcommand &s, snowflake guild_id); /** * @brief Delete a slash command local to a guild @@ -158,7 +158,7 @@ awaitable co_guild_command_create(const slashcommand &s * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_command_delete(snowflake id, snowflake guild_id); +[[nodiscard]] async co_guild_command_delete(snowflake id, snowflake guild_id); /** * @brief Edit slash command permissions of a guild @@ -171,7 +171,7 @@ awaitable co_guild_command_delete(snowflake id, snowfla * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_command_edit_permissions(const slashcommand &s, snowflake guild_id); +[[nodiscard]] async co_guild_command_edit_permissions(const slashcommand &s, snowflake guild_id); /** * @brief Get a slash command of a guild @@ -184,7 +184,7 @@ awaitable co_guild_command_edit_permissions(const slash * @return slashcommand returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_command_get(snowflake id, snowflake guild_id); +[[nodiscard]] async co_guild_command_get(snowflake id, snowflake guild_id); /** * @brief Get the permissions for a slash command of a guild @@ -196,7 +196,7 @@ awaitable co_guild_command_get(snowflake id, snowflake * @return guild_command_permissions returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_command_get_permissions(snowflake id, snowflake guild_id); +[[nodiscard]] async co_guild_command_get_permissions(snowflake id, snowflake guild_id); /** * @brief Edit a slash command local to a guild @@ -208,7 +208,7 @@ awaitable co_guild_command_get_permissions(snowflake id * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_command_edit(const slashcommand &s, snowflake guild_id); +[[nodiscard]] async co_guild_command_edit(const slashcommand &s, snowflake guild_id); /** * @brief Get the application's slash commands for a guild @@ -220,7 +220,7 @@ awaitable co_guild_command_edit(const slashcommand &s, * @return slashcommand_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_commands_get(snowflake guild_id); +[[nodiscard]] async co_guild_commands_get(snowflake guild_id); /** * @brief Respond to a slash command @@ -233,7 +233,7 @@ awaitable co_guild_commands_get(snowflake guild_id); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_interaction_response_create(snowflake interaction_id, const std::string &token, const interaction_response &r); +[[nodiscard]] async co_interaction_response_create(snowflake interaction_id, const std::string &token, const interaction_response &r); /** * @brief Edit response to a slash command @@ -245,7 +245,7 @@ awaitable co_interaction_response_create(snowflake inte * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_interaction_response_edit(const std::string &token, const message &m); +[[nodiscard]] async co_interaction_response_edit(const std::string &token, const message &m); /** * @brief Get the original response to a slash command @@ -256,7 +256,7 @@ awaitable co_interaction_response_edit(const std::strin * @return message returned object on completion * \memberof dpp::cluster */ -awaitable co_interaction_response_get_original(const std::string &token); +[[nodiscard]] async co_interaction_response_get_original(const std::string &token); /** * @brief Create a followup message to a slash command @@ -268,7 +268,7 @@ awaitable co_interaction_response_get_original(const st * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_interaction_followup_create(const std::string &token, const message &m); +[[nodiscard]] async co_interaction_followup_create(const std::string &token, const message &m); /** * @brief Edit original followup message to a slash command @@ -281,7 +281,7 @@ awaitable co_interaction_followup_create(const std::str * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_interaction_followup_edit_original(const std::string &token, const message &m); +[[nodiscard]] async co_interaction_followup_edit_original(const std::string &token, const message &m); /** * @brief Delete the initial interaction response @@ -292,7 +292,7 @@ awaitable co_interaction_followup_edit_original(const s * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_interaction_followup_delete(const std::string &token); +[[nodiscard]] async co_interaction_followup_delete(const std::string &token); /** * @brief Edit followup message to a slash command @@ -305,7 +305,7 @@ awaitable co_interaction_followup_delete(const std::str * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_interaction_followup_edit(const std::string &token, const message &m); +[[nodiscard]] async co_interaction_followup_edit(const std::string &token, const message &m); /** * @brief Get the followup message to a slash command @@ -317,7 +317,7 @@ awaitable co_interaction_followup_edit(const std::strin * @return message returned object on completion * \memberof dpp::cluster */ -awaitable co_interaction_followup_get(const std::string &token, snowflake message_id); +[[nodiscard]] async co_interaction_followup_get(const std::string &token, snowflake message_id); /** * @brief Get the original followup message to a slash command @@ -329,7 +329,7 @@ awaitable co_interaction_followup_get(const std::string * @return message returned object on completion * \memberof dpp::cluster */ -awaitable co_interaction_followup_get_original(const std::string &token); +[[nodiscard]] async co_interaction_followup_get_original(const std::string &token); /** * @brief Get all auto moderation rules for a guild @@ -338,7 +338,7 @@ awaitable co_interaction_followup_get_original(const st * @return automod_rule_map returned object on completion * \memberof dpp::cluster */ -awaitable co_automod_rules_get(snowflake guild_id); +[[nodiscard]] async co_automod_rules_get(snowflake guild_id); /** * @brief Get a single auto moderation rule @@ -348,7 +348,7 @@ awaitable co_automod_rules_get(snowflake guild_id); * @return automod_rule returned object on completion * \memberof dpp::cluster */ -awaitable co_automod_rule_get(snowflake guild_id, snowflake rule_id); +[[nodiscard]] async co_automod_rule_get(snowflake guild_id, snowflake rule_id); /** * @brief Create an auto moderation rule @@ -358,7 +358,7 @@ awaitable co_automod_rule_get(snowflake guild_id, snowf * @return automod_rule returned object on completion * \memberof dpp::cluster */ -awaitable co_automod_rule_create(snowflake guild_id, const automod_rule& r); +[[nodiscard]] async co_automod_rule_create(snowflake guild_id, const automod_rule& r); /** * @brief Edit an auto moderation rule @@ -368,7 +368,7 @@ awaitable co_automod_rule_create(snowflake guild_id, co * @return automod_rule returned object on completion * \memberof dpp::cluster */ -awaitable co_automod_rule_edit(snowflake guild_id, const automod_rule& r); +[[nodiscard]] async co_automod_rule_edit(snowflake guild_id, const automod_rule& r); /** * @brief Delete an auto moderation rule @@ -378,7 +378,7 @@ awaitable co_automod_rule_edit(snowflake guild_id, cons * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_automod_rule_delete(snowflake guild_id, snowflake rule_id); +[[nodiscard]] async co_automod_rule_delete(snowflake guild_id, snowflake rule_id); /** * @brief Create a channel @@ -396,7 +396,7 @@ awaitable co_automod_rule_delete(snowflake guild_id, sn * @return channel returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_create(const class channel &c); +[[nodiscard]] async co_channel_create(const class channel &c); /** * @brief Remove a permission from a channel @@ -408,7 +408,7 @@ awaitable co_channel_create(const class channel &c); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_delete_permission(const class channel &c, snowflake overwrite_id); +[[nodiscard]] async co_channel_delete_permission(const class channel &c, snowflake overwrite_id); /** * @brief Delete a channel @@ -419,7 +419,7 @@ awaitable co_channel_delete_permission(const class chan * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_delete(snowflake channel_id); +[[nodiscard]] async co_channel_delete(snowflake channel_id); /** * @brief Edit a channel's permissions @@ -435,7 +435,7 @@ awaitable co_channel_delete(snowflake channel_id); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_edit_permissions(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); +[[nodiscard]] async co_channel_edit_permissions(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); /** * @brief Edit a channel's permissions @@ -451,7 +451,7 @@ awaitable co_channel_edit_permissions(const class chann * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_edit_permissions(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); +[[nodiscard]] async co_channel_edit_permissions(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); /** * @brief Edit multiple channels positions @@ -466,7 +466,7 @@ awaitable co_channel_edit_permissions(const snowflake c * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_edit_positions(const std::vector &c); +[[nodiscard]] async co_channel_edit_positions(const std::vector &c); /** * @brief Edit a channel @@ -477,7 +477,7 @@ awaitable co_channel_edit_positions(const std::vector co_channel_edit(const class channel &c); +[[nodiscard]] async co_channel_edit(const class channel &c); /** * @brief Follow an announcement (news) channel @@ -488,7 +488,7 @@ awaitable co_channel_edit(const class channel &c); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_follow_news(const class channel &c, snowflake target_channel_id); +[[nodiscard]] async co_channel_follow_news(const class channel &c, snowflake target_channel_id); /** * @brief Get a channel @@ -499,7 +499,7 @@ awaitable co_channel_follow_news(const class channel &c * @return channel returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_get(snowflake c); +[[nodiscard]] async co_channel_get(snowflake c); /** * @brief Create invite for a channel @@ -511,7 +511,7 @@ awaitable co_channel_get(snowflake c); * @return invite returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_invite_create(const class channel &c, const class invite &i); +[[nodiscard]] async co_channel_invite_create(const class channel &c, const class invite &i); /** * @brief Get invites for a channel @@ -522,7 +522,7 @@ awaitable co_channel_invite_create(const class channel * @return invite_map returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_invites_get(const class channel &c); +[[nodiscard]] async co_channel_invites_get(const class channel &c); /** * @brief Trigger channel typing indicator @@ -532,7 +532,7 @@ awaitable co_channel_invites_get(const class channel &c * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_typing(const class channel &c); +[[nodiscard]] async co_channel_typing(const class channel &c); /** * @brief Trigger channel typing indicator @@ -542,7 +542,7 @@ awaitable co_channel_typing(const class channel &c); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_typing(snowflake cid); +[[nodiscard]] async co_channel_typing(snowflake cid); /** * @brief Get all channels for a guild @@ -553,7 +553,7 @@ awaitable co_channel_typing(snowflake cid); * @return channel_map returned object on completion * \memberof dpp::cluster */ -awaitable co_channels_get(snowflake guild_id); +[[nodiscard]] async co_channels_get(snowflake guild_id); /** * @brief Create a dm channel @@ -563,7 +563,7 @@ awaitable co_channels_get(snowflake guild_id); * @return channel returned object on completion * \memberof dpp::cluster */ -awaitable co_create_dm_channel(snowflake user_id); +[[nodiscard]] async co_create_dm_channel(snowflake user_id); /** * @brief Get current user DM channels @@ -571,7 +571,7 @@ awaitable co_create_dm_channel(snowflake user_id); * @return channel_map returned object on completion * \memberof dpp::cluster */ -awaitable co_current_user_get_dms(); +[[nodiscard]] async co_current_user_get_dms(); /** * @brief Create a direct message, also create the channel for the direct message if needed @@ -585,7 +585,7 @@ awaitable co_current_user_get_dms(); * @return message returned object on completion * \memberof dpp::cluster */ -awaitable co_direct_message_create(snowflake user_id, const message &m); +[[nodiscard]] async co_direct_message_create(snowflake user_id, const message &m); /** * @brief Adds a recipient to a Group DM using their access token @@ -598,7 +598,7 @@ awaitable co_direct_message_create(snowflake user_id, c * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_gdm_add(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick); +[[nodiscard]] async co_gdm_add(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick); /** * @brief Removes a recipient from a Group DM @@ -609,7 +609,7 @@ awaitable co_gdm_add(snowflake channel_id, snowflake us * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_gdm_remove(snowflake channel_id, snowflake user_id); +[[nodiscard]] async co_gdm_remove(snowflake channel_id, snowflake user_id); /** * @brief Create single emoji. @@ -623,7 +623,7 @@ awaitable co_gdm_remove(snowflake channel_id, snowflake * @return emoji returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_emoji_create(snowflake guild_id, const class emoji& newemoji); +[[nodiscard]] async co_guild_emoji_create(snowflake guild_id, const class emoji& newemoji); /** * @brief Delete a guild emoji @@ -636,7 +636,7 @@ awaitable co_guild_emoji_create(snowflake guild_id, con * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_emoji_delete(snowflake guild_id, snowflake emoji_id); +[[nodiscard]] async co_guild_emoji_delete(snowflake guild_id, snowflake emoji_id); /** * @brief Edit a single emoji. @@ -650,7 +650,7 @@ awaitable co_guild_emoji_delete(snowflake guild_id, sno * @return emoji returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_emoji_edit(snowflake guild_id, const class emoji& newemoji); +[[nodiscard]] async co_guild_emoji_edit(snowflake guild_id, const class emoji& newemoji); /** * @brief Get a single emoji @@ -662,7 +662,7 @@ awaitable co_guild_emoji_edit(snowflake guild_id, const * @return emoji returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_emoji_get(snowflake guild_id, snowflake emoji_id); +[[nodiscard]] async co_guild_emoji_get(snowflake guild_id, snowflake emoji_id); /** * @brief Get all emojis for a guild @@ -673,7 +673,7 @@ awaitable co_guild_emoji_get(snowflake guild_id, snowfl * @return emoji_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_emojis_get(snowflake guild_id); +[[nodiscard]] async co_guild_emojis_get(snowflake guild_id); /** * @brief Get the gateway information for the bot using the token @@ -682,7 +682,7 @@ awaitable co_guild_emojis_get(snowflake guild_id); * @return gateway returned object on completion * \memberof dpp::cluster */ -awaitable co_get_gateway_bot(); +[[nodiscard]] async co_get_gateway_bot(); /** * @brief Modify current member @@ -698,7 +698,7 @@ awaitable co_get_gateway_bot(); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_current_member_edit(snowflake guild_id, const std::string &nickname); +[[nodiscard]] async co_guild_current_member_edit(snowflake guild_id, const std::string &nickname); /** * @brief Get the audit log for a guild @@ -714,7 +714,7 @@ awaitable co_guild_current_member_edit(snowflake guild_ * @return auditlog returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_auditlog_get(snowflake guild_id, snowflake user_id, uint32_t action_type, snowflake before, snowflake after, uint32_t limit); +[[nodiscard]] async co_guild_auditlog_get(snowflake guild_id, snowflake user_id, uint32_t action_type, snowflake before, snowflake after, uint32_t limit); /** * @brief Add guild ban @@ -730,7 +730,7 @@ awaitable co_guild_auditlog_get(snowflake guild_id, sno * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_ban_add(snowflake guild_id, snowflake user_id, uint32_t delete_message_seconds = 0); +[[nodiscard]] async co_guild_ban_add(snowflake guild_id, snowflake user_id, uint32_t delete_message_seconds = 0); /** * @brief Delete guild ban @@ -745,7 +745,7 @@ awaitable co_guild_ban_add(snowflake guild_id, snowflak * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_ban_delete(snowflake guild_id, snowflake user_id); +[[nodiscard]] async co_guild_ban_delete(snowflake guild_id, snowflake user_id); /** * @brief Create a guild @@ -768,7 +768,7 @@ awaitable co_guild_ban_delete(snowflake guild_id, snowf * @return guild returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_create(const class guild &g); +[[nodiscard]] async co_guild_create(const class guild &g); /** * @brief Delete a guild @@ -781,7 +781,7 @@ awaitable co_guild_create(const class guild &g); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_delete(snowflake guild_id); +[[nodiscard]] async co_guild_delete(snowflake guild_id); /** * @brief Delete guild integration @@ -797,7 +797,7 @@ awaitable co_guild_delete(snowflake guild_id); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_delete_integration(snowflake guild_id, snowflake integration_id); +[[nodiscard]] async co_guild_delete_integration(snowflake guild_id, snowflake integration_id); /** * @brief Edit a guild @@ -812,7 +812,7 @@ awaitable co_guild_delete_integration(snowflake guild_i * @return guild returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_edit(const class guild &g); +[[nodiscard]] async co_guild_edit(const class guild &g); /** * @brief Edit guild widget @@ -827,7 +827,7 @@ awaitable co_guild_edit(const class guild &g); * @return guild_widget returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_edit_widget(snowflake guild_id, const class guild_widget &gw); +[[nodiscard]] async co_guild_edit_widget(snowflake guild_id, const class guild_widget &gw); /** * @brief Get single guild ban @@ -840,7 +840,7 @@ awaitable co_guild_edit_widget(snowflake guild_id, cons * @return ban returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_ban(snowflake guild_id, snowflake user_id); +[[nodiscard]] async co_guild_get_ban(snowflake guild_id, snowflake user_id); /** * @brief Get guild ban list @@ -856,10 +856,10 @@ awaitable co_guild_get_ban(snowflake guild_id, snowflak * @return ban_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_bans(snowflake guild_id, snowflake before, snowflake after, snowflake limit); +[[nodiscard]] async co_guild_get_bans(snowflake guild_id, snowflake before, snowflake after, snowflake limit); -awaitable co_guild_get(snowflake guild_id); +[[nodiscard]] async co_guild_get(snowflake guild_id); /** * @brief Get guild integrations @@ -874,10 +874,10 @@ awaitable co_guild_get(snowflake guild_id); * @note This endpoint returns a maximum of 50 integrations. If a guild has more integrations, they cannot be accessed. * \memberof dpp::cluster */ -awaitable co_guild_get_integrations(snowflake guild_id); +[[nodiscard]] async co_guild_get_integrations(snowflake guild_id); -awaitable co_guild_get_preview(snowflake guild_id); +[[nodiscard]] async co_guild_get_preview(snowflake guild_id); /** * @brief Get guild vanity url, if enabled @@ -889,7 +889,7 @@ awaitable co_guild_get_preview(snowflake guild_id); * @return invite returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_vanity(snowflake guild_id); +[[nodiscard]] async co_guild_get_vanity(snowflake guild_id); /** * @brief Get guild widget @@ -902,7 +902,7 @@ awaitable co_guild_get_vanity(snowflake guild_id); * @return guild_widget returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_widget(snowflake guild_id); +[[nodiscard]] async co_guild_get_widget(snowflake guild_id); /** * @brief Modify guild integration @@ -915,7 +915,7 @@ awaitable co_guild_get_widget(snowflake guild_id); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_modify_integration(snowflake guild_id, const class integration &i); +[[nodiscard]] async co_guild_modify_integration(snowflake guild_id, const class integration &i); /** * @brief Get prune counts @@ -932,7 +932,7 @@ awaitable co_guild_modify_integration(snowflake guild_i * @return prune returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_prune_counts(snowflake guild_id, const struct prune& pruneinfo); +[[nodiscard]] async co_guild_get_prune_counts(snowflake guild_id, const struct prune& pruneinfo); /** * @brief Begin guild prune @@ -951,7 +951,7 @@ awaitable co_guild_get_prune_counts(snowflake guild_id, * @return prune returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_begin_prune(snowflake guild_id, const struct prune& pruneinfo); +[[nodiscard]] async co_guild_begin_prune(snowflake guild_id, const struct prune& pruneinfo); /** * @brief Change current user nickname @@ -968,7 +968,7 @@ awaitable co_guild_begin_prune(snowflake guild_id, cons * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_set_nickname(snowflake guild_id, const std::string &nickname); +[[nodiscard]] async co_guild_set_nickname(snowflake guild_id, const std::string &nickname); /** * @brief Sync guild integration @@ -980,7 +980,7 @@ awaitable co_guild_set_nickname(snowflake guild_id, con * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_sync_integration(snowflake guild_id, snowflake integration_id); +[[nodiscard]] async co_guild_sync_integration(snowflake guild_id, snowflake integration_id); /** * @brief Get the guild's onboarding configuration @@ -991,7 +991,7 @@ awaitable co_guild_sync_integration(snowflake guild_id, * @return onboarding returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_onboarding(snowflake guild_id); +[[nodiscard]] async co_guild_get_onboarding(snowflake guild_id); /** * @brief Edit the guild's onboarding configuration @@ -1007,7 +1007,7 @@ awaitable co_guild_get_onboarding(snowflake guild_id); * @return onboarding returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_edit_onboarding(const struct onboarding& o); +[[nodiscard]] async co_guild_edit_onboarding(const struct onboarding& o); /** * @brief Get the guild's welcome screen @@ -1020,7 +1020,7 @@ awaitable co_guild_edit_onboarding(const struct onboard * @return dpp::welcome_screen returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_welcome_screen(snowflake guild_id); +[[nodiscard]] async co_guild_get_welcome_screen(snowflake guild_id); /** * @brief Edit the guild's welcome screen @@ -1035,7 +1035,7 @@ awaitable co_guild_get_welcome_screen(snowflake guild_i * @return dpp::welcome_screen returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_edit_welcome_screen(snowflake guild_id, const struct welcome_screen& welcome_screen, bool enabled); +[[nodiscard]] async co_guild_edit_welcome_screen(snowflake guild_id, const struct welcome_screen& welcome_screen, bool enabled); /** * @brief Add guild member. Needs a specific oauth2 scope, from which you get the access_token. @@ -1055,7 +1055,7 @@ awaitable co_guild_edit_welcome_screen(snowflake guild_ * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_add_member(const guild_member& gm, const std::string &access_token); +[[nodiscard]] async co_guild_add_member(const guild_member& gm, const std::string &access_token); /** * @brief Edit the properties of an existing guild member @@ -1071,7 +1071,7 @@ awaitable co_guild_add_member(const guild_member& gm, c * @return guild_member returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_edit_member(const guild_member& gm); +[[nodiscard]] async co_guild_edit_member(const guild_member& gm); /** * @brief Get a guild member @@ -1082,7 +1082,7 @@ awaitable co_guild_edit_member(const guild_member& gm); * @return guild_member returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_member(snowflake guild_id, snowflake user_id); +[[nodiscard]] async co_guild_get_member(snowflake guild_id, snowflake user_id); /** * @brief Get all guild members @@ -1096,7 +1096,7 @@ awaitable co_guild_get_member(snowflake guild_id, snowf * @return guild_member_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_members(snowflake guild_id, uint16_t limit, snowflake after); +[[nodiscard]] async co_guild_get_members(snowflake guild_id, uint16_t limit, snowflake after); /** * @brief Add role to guild member @@ -1112,7 +1112,7 @@ awaitable co_guild_get_members(snowflake guild_id, uint * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_member_add_role(snowflake guild_id, snowflake user_id, snowflake role_id); +[[nodiscard]] async co_guild_member_add_role(snowflake guild_id, snowflake user_id, snowflake role_id); /** * @brief Remove (kick) a guild member @@ -1128,7 +1128,7 @@ awaitable co_guild_member_add_role(snowflake guild_id, * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_member_delete(snowflake guild_id, snowflake user_id); +[[nodiscard]] async co_guild_member_delete(snowflake guild_id, snowflake user_id); /** * @brief Remove (kick) a guild member @@ -1143,7 +1143,7 @@ awaitable co_guild_member_delete(snowflake guild_id, sn * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_member_kick(snowflake guild_id, snowflake user_id); +[[nodiscard]] async co_guild_member_kick(snowflake guild_id, snowflake user_id); /** * @brief Set the timeout of a guild member @@ -1158,7 +1158,7 @@ awaitable co_guild_member_kick(snowflake guild_id, snow * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_member_timeout(snowflake guild_id, snowflake user_id, time_t communication_disabled_until); +[[nodiscard]] async co_guild_member_timeout(snowflake guild_id, snowflake user_id, time_t communication_disabled_until); /** * @brief Remove role from guild member @@ -1175,7 +1175,7 @@ awaitable co_guild_member_timeout(snowflake guild_id, s * @deprecated Use dpp::cluster::guild_member_remove_role instead * \memberof dpp::cluster */ -awaitable co_guild_member_delete_role(snowflake guild_id, snowflake user_id, snowflake role_id); +[[nodiscard]] async co_guild_member_delete_role(snowflake guild_id, snowflake user_id, snowflake role_id); /** * @brief Remove role from guild member @@ -1191,7 +1191,7 @@ awaitable co_guild_member_delete_role(snowflake guild_i * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_member_remove_role(snowflake guild_id, snowflake user_id, snowflake role_id); +[[nodiscard]] async co_guild_member_remove_role(snowflake guild_id, snowflake user_id, snowflake role_id); /** * @brief Moves the guild member to a other voice channel, if member is connected to one. @@ -1208,7 +1208,7 @@ awaitable co_guild_member_remove_role(snowflake guild_i * @return guild_member returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_member_move(const snowflake channel_id, const snowflake guild_id, const snowflake user_id); +[[nodiscard]] async co_guild_member_move(const snowflake channel_id, const snowflake guild_id, const snowflake user_id); /** * @brief Search for guild members based on whether their username or nickname starts with the given string. @@ -1222,7 +1222,7 @@ awaitable co_guild_member_move(const snowflake channel_ * @return guild_member_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_search_members(snowflake guild_id, const std::string& query, uint16_t limit); +[[nodiscard]] async co_guild_search_members(snowflake guild_id, const std::string& query, uint16_t limit); /** * @brief Get guild invites @@ -1235,10 +1235,10 @@ awaitable co_guild_search_members(snowflake guild_id, c * @return invite_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_invites(snowflake guild_id); +[[nodiscard]] async co_guild_get_invites(snowflake guild_id); -awaitable co_invite_delete(const std::string &invitecode); +[[nodiscard]] async co_invite_delete(const std::string &invitecode); /** * @brief Get details about an invite @@ -1249,7 +1249,7 @@ awaitable co_invite_delete(const std::string &invitecod * @return invite returned object on completion * \memberof dpp::cluster */ -awaitable co_invite_get(const std::string &invite_code); +[[nodiscard]] async co_invite_get(const std::string &invite_code); /** * @brief Add a reaction to a message. The reaction string must be either an `emojiname:id` or a unicode character. @@ -1261,7 +1261,7 @@ awaitable co_invite_get(const std::string &invite_code) * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_add_reaction(const struct message &m, const std::string &reaction); +[[nodiscard]] async co_message_add_reaction(const struct message &m, const std::string &reaction); /** * @brief Add a reaction to a message by id. The reaction string must be either an `emojiname:id` or a unicode character. @@ -1274,7 +1274,7 @@ awaitable co_message_add_reaction(const struct message * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_add_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction); +[[nodiscard]] async co_message_add_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction); /** * @brief Send a message to a channel. The callback function is called when the message has been sent @@ -1285,7 +1285,7 @@ awaitable co_message_add_reaction(snowflake message_id, * @return message returned object on completion * \memberof dpp::cluster */ -awaitable co_message_create(const struct message &m); +[[nodiscard]] async co_message_create(const struct message &m); /** * @brief Crosspost a message. The callback function is called when the message has been sent @@ -1297,7 +1297,7 @@ awaitable co_message_create(const struct message &m); * @return message returned object on completion * \memberof dpp::cluster */ -awaitable co_message_crosspost(snowflake message_id, snowflake channel_id); +[[nodiscard]] async co_message_crosspost(snowflake message_id, snowflake channel_id); /** * @brief Delete all reactions on a message @@ -1308,7 +1308,7 @@ awaitable co_message_crosspost(snowflake message_id, sn * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_delete_all_reactions(const struct message &m); +[[nodiscard]] async co_message_delete_all_reactions(const struct message &m); /** * @brief Delete all reactions on a message by id @@ -1320,7 +1320,7 @@ awaitable co_message_delete_all_reactions(const struct * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_delete_all_reactions(snowflake message_id, snowflake channel_id); +[[nodiscard]] async co_message_delete_all_reactions(snowflake message_id, snowflake channel_id); /** * @brief Bulk delete messages from a channel. The callback function is called when the message has been edited @@ -1335,7 +1335,7 @@ awaitable co_message_delete_all_reactions(snowflake mes * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_delete_bulk(const std::vector &message_ids, snowflake channel_id); +[[nodiscard]] async co_message_delete_bulk(const std::vector &message_ids, snowflake channel_id); /** * @brief Delete a message from a channel. The callback function is called when the message has been edited @@ -1348,7 +1348,7 @@ awaitable co_message_delete_bulk(const std::vector co_message_delete(snowflake message_id, snowflake channel_id); +[[nodiscard]] async co_message_delete(snowflake message_id, snowflake channel_id); /** * @brief Delete own reaction from a message. The reaction string must be either an `emojiname:id` or a unicode character. @@ -1360,7 +1360,7 @@ awaitable co_message_delete(snowflake message_id, snowf * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_delete_own_reaction(const struct message &m, const std::string &reaction); +[[nodiscard]] async co_message_delete_own_reaction(const struct message &m, const std::string &reaction); /** * @brief Delete own reaction from a message by id. The reaction string must be either an `emojiname:id` or a unicode character. @@ -1373,7 +1373,7 @@ awaitable co_message_delete_own_reaction(const struct m * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_delete_own_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction); +[[nodiscard]] async co_message_delete_own_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction); /** * @brief Delete a user's reaction from a message. The reaction string must be either an `emojiname:id` or a unicode character @@ -1386,7 +1386,7 @@ awaitable co_message_delete_own_reaction(snowflake mess * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_delete_reaction(const struct message &m, snowflake user_id, const std::string &reaction); +[[nodiscard]] async co_message_delete_reaction(const struct message &m, snowflake user_id, const std::string &reaction); /** * @brief Delete a user's reaction from a message by id. The reaction string must be either an `emojiname:id` or a unicode character @@ -1400,7 +1400,7 @@ awaitable co_message_delete_reaction(const struct messa * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_delete_reaction(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction); +[[nodiscard]] async co_message_delete_reaction(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction); /** * @brief Delete all reactions on a message using a particular emoji. The reaction string must be either an `emojiname:id` or a unicode character @@ -1412,7 +1412,7 @@ awaitable co_message_delete_reaction(snowflake message_ * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_delete_reaction_emoji(const struct message &m, const std::string &reaction); +[[nodiscard]] async co_message_delete_reaction_emoji(const struct message &m, const std::string &reaction); /** * @brief Delete all reactions on a message using a particular emoji by id. The reaction string must be either an `emojiname:id` or a unicode character @@ -1425,7 +1425,7 @@ awaitable co_message_delete_reaction_emoji(const struct * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_delete_reaction_emoji(snowflake message_id, snowflake channel_id, const std::string &reaction); +[[nodiscard]] async co_message_delete_reaction_emoji(snowflake message_id, snowflake channel_id, const std::string &reaction); /** * @brief Edit a message on a channel. The callback function is called when the message has been edited @@ -1436,7 +1436,7 @@ awaitable co_message_delete_reaction_emoji(snowflake me * @return message returned object on completion * \memberof dpp::cluster */ -awaitable co_message_edit(const struct message &m); +[[nodiscard]] async co_message_edit(const struct message &m); /** * @brief Get a message @@ -1448,7 +1448,7 @@ awaitable co_message_edit(const struct message &m); * @return message returned object on completion * \memberof dpp::cluster */ -awaitable co_message_get(snowflake message_id, snowflake channel_id); +[[nodiscard]] async co_message_get(snowflake message_id, snowflake channel_id); /** * @brief Get reactions on a message for a particular emoji. The reaction string must be either an `emojiname:id` or a unicode character @@ -1463,7 +1463,7 @@ awaitable co_message_get(snowflake message_id, snowflak * @return user_map returned object on completion * \memberof dpp::cluster */ -awaitable co_message_get_reactions(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit); +[[nodiscard]] async co_message_get_reactions(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit); /** * @brief Get reactions on a message for a particular emoji by id. The reaction string must be either an `emojiname:id` or a unicode character @@ -1479,7 +1479,7 @@ awaitable co_message_get_reactions(const struct message * @return emoji_map returned object on completion * \memberof dpp::cluster */ -awaitable co_message_get_reactions(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit); +[[nodiscard]] async co_message_get_reactions(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit); /** * @brief Pin a message @@ -1491,7 +1491,7 @@ awaitable co_message_get_reactions(snowflake message_id * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_pin(snowflake channel_id, snowflake message_id); +[[nodiscard]] async co_message_pin(snowflake channel_id, snowflake message_id); /** * @brief Get multiple messages. @@ -1508,7 +1508,7 @@ awaitable co_message_pin(snowflake channel_id, snowflak * @return message_map returned object on completion * \memberof dpp::cluster */ -awaitable co_messages_get(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit); +[[nodiscard]] async co_messages_get(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit); /** * @brief Unpin a message @@ -1520,7 +1520,7 @@ awaitable co_messages_get(snowflake channel_id, snowfla * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_message_unpin(snowflake channel_id, snowflake message_id); +[[nodiscard]] async co_message_unpin(snowflake channel_id, snowflake message_id); /** * @brief Get a channel's pins @@ -1530,7 +1530,7 @@ awaitable co_message_unpin(snowflake channel_id, snowfl * @return message_map returned object on completion * \memberof dpp::cluster */ -awaitable co_channel_pins_get(snowflake channel_id); +[[nodiscard]] async co_channel_pins_get(snowflake channel_id); /** * @brief Create a role on a guild @@ -1545,7 +1545,7 @@ awaitable co_channel_pins_get(snowflake channel_id); * @return role returned object on completion * \memberof dpp::cluster */ -awaitable co_role_create(const class role &r); +[[nodiscard]] async co_role_create(const class role &r); /** * @brief Delete a role @@ -1560,7 +1560,7 @@ awaitable co_role_create(const class role &r); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_role_delete(snowflake guild_id, snowflake role_id); +[[nodiscard]] async co_role_delete(snowflake guild_id, snowflake role_id); /** * @brief Edit a role on a guild @@ -1574,7 +1574,7 @@ awaitable co_role_delete(snowflake guild_id, snowflake * @return role returned object on completion * \memberof dpp::cluster */ -awaitable co_role_edit(const class role &r); +[[nodiscard]] async co_role_edit(const class role &r); /** * @brief Edit multiple role's position in a guild. Returns a list of all roles of the guild on success. @@ -1590,7 +1590,7 @@ awaitable co_role_edit(const class role &r); * @return role_map returned object on completion * \memberof dpp::cluster */ -awaitable co_roles_edit_position(snowflake guild_id, const std::vector &roles); +[[nodiscard]] async co_roles_edit_position(snowflake guild_id, const std::vector &roles); /** * @brief Get a role for a guild @@ -1601,7 +1601,7 @@ awaitable co_roles_edit_position(snowflake guild_id, co * @return role_map returned object on completion * \memberof dpp::cluster */ -awaitable co_roles_get(snowflake guild_id); +[[nodiscard]] async co_roles_get(snowflake guild_id); /** * @brief Get the application's role connection metadata records @@ -1612,7 +1612,7 @@ awaitable co_roles_get(snowflake guild_id); * @return application_role_connection returned object on completion * \memberof dpp::cluster */ -awaitable co_application_role_connection_get(snowflake application_id); +[[nodiscard]] async co_application_role_connection_get(snowflake application_id); /** * @brief Update the application's role connection metadata records @@ -1625,7 +1625,7 @@ awaitable co_application_role_connection_get(snowflake * @note An application can have a maximum of 5 metadata records. * \memberof dpp::cluster */ -awaitable co_application_role_connection_update(snowflake application_id, const std::vector &connection_metadata); +[[nodiscard]] async co_application_role_connection_update(snowflake application_id, const std::vector &connection_metadata); /** * @brief Get user application role connection @@ -1636,7 +1636,7 @@ awaitable co_application_role_connection_update(snowfla * @return application_role_connection returned object on completion * \memberof dpp::cluster */ -awaitable co_user_application_role_connection_get(snowflake application_id); +[[nodiscard]] async co_user_application_role_connection_get(snowflake application_id); /** * @brief Update user application role connection @@ -1648,7 +1648,7 @@ awaitable co_user_application_role_connection_get(snowf * @return application_role_connection returned object on completion * \memberof dpp::cluster */ -awaitable co_user_application_role_connection_update(snowflake application_id, const application_role_connection &connection); +[[nodiscard]] async co_user_application_role_connection_update(snowflake application_id, const application_role_connection &connection); /** * @brief Get all scheduled events for a guild @@ -1658,7 +1658,7 @@ awaitable co_user_application_role_connection_update(sn * @return scheduled_event_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_events_get(snowflake guild_id); +[[nodiscard]] async co_guild_events_get(snowflake guild_id); /** * @brief Create a scheduled event on a guild @@ -1669,7 +1669,7 @@ awaitable co_guild_events_get(snowflake guild_id); * @return scheduled_event returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_event_create(const scheduled_event& event); +[[nodiscard]] async co_guild_event_create(const scheduled_event& event); /** * @brief Delete a scheduled event from a guild @@ -1681,7 +1681,7 @@ awaitable co_guild_event_create(const scheduled_event& * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_event_delete(snowflake event_id, snowflake guild_id); +[[nodiscard]] async co_guild_event_delete(snowflake event_id, snowflake guild_id); /** * @brief Edit/modify a scheduled event on a guild @@ -1692,7 +1692,7 @@ awaitable co_guild_event_delete(snowflake event_id, sno * @return scheduled_event returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_event_edit(const scheduled_event& event); +[[nodiscard]] async co_guild_event_edit(const scheduled_event& event); /** * @brief Get a scheduled event for a guild @@ -1704,10 +1704,10 @@ awaitable co_guild_event_edit(const scheduled_event& ev * @return scheduled_event returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_event_get(snowflake guild_id, snowflake event_id); +[[nodiscard]] async co_guild_event_get(snowflake guild_id, snowflake event_id); -awaitable co_stage_instance_create(const stage_instance& si); +[[nodiscard]] async co_stage_instance_create(const stage_instance& si); /** * @brief Get the stage instance associated with the channel id, if it exists. @@ -1717,10 +1717,10 @@ awaitable co_stage_instance_create(const stage_instance * @return stage_instance returned object on completion * \memberof dpp::cluster */ -awaitable co_stage_instance_get(const snowflake channel_id); +[[nodiscard]] async co_stage_instance_get(const snowflake channel_id); -awaitable co_stage_instance_edit(const stage_instance& si); +[[nodiscard]] async co_stage_instance_edit(const stage_instance& si); /** * @brief Delete a stage instance. @@ -1731,7 +1731,7 @@ awaitable co_stage_instance_edit(const stage_instance& * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. * \memberof dpp::cluster */ -awaitable co_stage_instance_delete(const snowflake channel_id); +[[nodiscard]] async co_stage_instance_delete(const snowflake channel_id); /** * @brief Create a sticker in a guild @@ -1742,7 +1742,7 @@ awaitable co_stage_instance_delete(const snowflake chan * @return sticker returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_sticker_create(const sticker &s); +[[nodiscard]] async co_guild_sticker_create(const sticker &s); /** * @brief Delete a sticker from a guild @@ -1754,7 +1754,7 @@ awaitable co_guild_sticker_create(const sticker &s); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_sticker_delete(snowflake sticker_id, snowflake guild_id); +[[nodiscard]] async co_guild_sticker_delete(snowflake sticker_id, snowflake guild_id); /** * @brief Get a guild sticker @@ -1765,7 +1765,7 @@ awaitable co_guild_sticker_delete(snowflake sticker_id, * @return sticker returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_sticker_get(snowflake id, snowflake guild_id); +[[nodiscard]] async co_guild_sticker_get(snowflake id, snowflake guild_id); /** * @brief Modify a sticker in a guild @@ -1776,7 +1776,7 @@ awaitable co_guild_sticker_get(snowflake id, snowflake * @return sticker returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_sticker_modify(const sticker &s); +[[nodiscard]] async co_guild_sticker_modify(const sticker &s); /** * @brief Get all guild stickers @@ -1786,7 +1786,7 @@ awaitable co_guild_sticker_modify(const sticker &s); * @return sticker_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_stickers_get(snowflake guild_id); +[[nodiscard]] async co_guild_stickers_get(snowflake guild_id); /** * @brief Get a nitro sticker @@ -1796,7 +1796,7 @@ awaitable co_guild_stickers_get(snowflake guild_id); * @return sticker returned object on completion * \memberof dpp::cluster */ -awaitable co_nitro_sticker_get(snowflake id); +[[nodiscard]] async co_nitro_sticker_get(snowflake id); /** * @brief Get sticker packs @@ -1805,7 +1805,7 @@ awaitable co_nitro_sticker_get(snowflake id); * @return sticker_pack_map returned object on completion * \memberof dpp::cluster */ -awaitable co_sticker_packs_get(); +[[nodiscard]] async co_sticker_packs_get(); /** * @brief Create a new guild based on a template. @@ -1817,7 +1817,7 @@ awaitable co_sticker_packs_get(); * @return guild returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_create_from_template(const std::string &code, const std::string &name); +[[nodiscard]] async co_guild_create_from_template(const std::string &code, const std::string &name); /** * @brief Creates a template for the guild @@ -1830,7 +1830,7 @@ awaitable co_guild_create_from_template(const std::stri * @return dtemplate returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_template_create(snowflake guild_id, const std::string &name, const std::string &description); +[[nodiscard]] async co_guild_template_create(snowflake guild_id, const std::string &name, const std::string &description); /** * @brief Deletes the template @@ -1842,7 +1842,7 @@ awaitable co_guild_template_create(snowflake guild_id, * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_template_delete(snowflake guild_id, const std::string &code); +[[nodiscard]] async co_guild_template_delete(snowflake guild_id, const std::string &code); /** * @brief Modifies the template's metadata. @@ -1856,7 +1856,7 @@ awaitable co_guild_template_delete(snowflake guild_id, * @return dtemplate returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_template_modify(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description); +[[nodiscard]] async co_guild_template_modify(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description); /** * @brief Get guild templates @@ -1867,7 +1867,7 @@ awaitable co_guild_template_modify(snowflake guild_id, * @return dtemplate_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_templates_get(snowflake guild_id); +[[nodiscard]] async co_guild_templates_get(snowflake guild_id); /** * @brief Syncs the template to the guild's current state. @@ -1879,7 +1879,7 @@ awaitable co_guild_templates_get(snowflake guild_id); * @return dtemplate returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_template_sync(snowflake guild_id, const std::string &code); +[[nodiscard]] async co_guild_template_sync(snowflake guild_id, const std::string &code); /** * @brief Get a template @@ -1889,7 +1889,7 @@ awaitable co_guild_template_sync(snowflake guild_id, co * @return dtemplate returned object on completion * \memberof dpp::cluster */ -awaitable co_template_get(const std::string &code); +[[nodiscard]] async co_template_get(const std::string &code); /** * @brief Join a thread @@ -1899,7 +1899,7 @@ awaitable co_template_get(const std::string &code); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_current_user_join_thread(snowflake thread_id); +[[nodiscard]] async co_current_user_join_thread(snowflake thread_id); /** * @brief Leave a thread @@ -1909,7 +1909,7 @@ awaitable co_current_user_join_thread(snowflake thread_ * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_current_user_leave_thread(snowflake thread_id); +[[nodiscard]] async co_current_user_leave_thread(snowflake thread_id); /** * @brief Get all active threads in the guild, including public and private threads. Threads are ordered by their id, in descending order. @@ -1919,7 +1919,7 @@ awaitable co_current_user_leave_thread(snowflake thread * @return active_threads returned object on completion * \memberof dpp::cluster */ -awaitable co_threads_get_active(snowflake guild_id); +[[nodiscard]] async co_threads_get_active(snowflake guild_id); /** * @brief Get private archived threads in a channel which current user has joined (Sorted by ID in descending order) @@ -1931,7 +1931,7 @@ awaitable co_threads_get_active(snowflake guild_id); * @return thread_map returned object on completion * \memberof dpp::cluster */ -awaitable co_threads_get_joined_private_archived(snowflake channel_id, snowflake before_id, uint16_t limit); +[[nodiscard]] async co_threads_get_joined_private_archived(snowflake channel_id, snowflake before_id, uint16_t limit); /** * @brief Get private archived threads in a channel (Sorted by archive_timestamp in descending order) @@ -1943,7 +1943,7 @@ awaitable co_threads_get_joined_private_archived(snowfl * @return thread_map returned object on completion * \memberof dpp::cluster */ -awaitable co_threads_get_private_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit); +[[nodiscard]] async co_threads_get_private_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit); /** * @brief Get public archived threads in a channel (Sorted by archive_timestamp in descending order) @@ -1955,7 +1955,7 @@ awaitable co_threads_get_private_archived(snowflake cha * @return thread_map returned object on completion * \memberof dpp::cluster */ -awaitable co_threads_get_public_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit); +[[nodiscard]] async co_threads_get_public_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit); /** * @brief Get a thread member @@ -1966,7 +1966,7 @@ awaitable co_threads_get_public_archived(snowflake chan * @return thread_member returned object on completion * \memberof dpp::cluster */ -awaitable co_thread_member_get(const snowflake thread_id, const snowflake user_id); +[[nodiscard]] async co_thread_member_get(const snowflake thread_id, const snowflake user_id); /** * @brief Get members of a thread @@ -1976,7 +1976,7 @@ awaitable co_thread_member_get(const snowflake thread_i * @return thread_member_map returned object on completion * \memberof dpp::cluster */ -awaitable co_thread_members_get(snowflake thread_id); +[[nodiscard]] async co_thread_members_get(snowflake thread_id); /** * @brief Create a thread in a forum or media channel @@ -1993,7 +1993,7 @@ awaitable co_thread_members_get(snowflake thread_id); * @return thread returned object on completion * \memberof dpp::cluster */ -awaitable co_thread_create_in_forum(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags = {}); +[[nodiscard]] async co_thread_create_in_forum(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags = {}); /** * @brief Create a thread @@ -2010,7 +2010,7 @@ awaitable co_thread_create_in_forum(const std::string& * @return thread returned object on completion * \memberof dpp::cluster */ -awaitable co_thread_create(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user); +[[nodiscard]] async co_thread_create(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user); /** * @brief Edit a thread @@ -2022,7 +2022,7 @@ awaitable co_thread_create(const std::string& thread_na * @return thread returned object on completion * \memberof dpp::cluster */ -awaitable co_thread_edit(const thread &t); +[[nodiscard]] async co_thread_edit(const thread &t); /** * @brief Create a thread with a message (Discord: ID of a thread is same as message ID) @@ -2037,7 +2037,7 @@ awaitable co_thread_edit(const thread &t); * @return thread returned object on completion * \memberof dpp::cluster */ -awaitable co_thread_create_with_message(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user); +[[nodiscard]] async co_thread_create_with_message(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user); /** * @brief Add a member to a thread @@ -2048,7 +2048,7 @@ awaitable co_thread_create_with_message(const std::stri * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_thread_member_add(snowflake thread_id, snowflake user_id); +[[nodiscard]] async co_thread_member_add(snowflake thread_id, snowflake user_id); /** * @brief Remove a member from a thread @@ -2059,7 +2059,7 @@ awaitable co_thread_member_add(snowflake thread_id, sno * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_thread_member_remove(snowflake thread_id, snowflake user_id); +[[nodiscard]] async co_thread_member_remove(snowflake thread_id, snowflake user_id); /** * @brief Edit current (bot) user @@ -2075,7 +2075,7 @@ awaitable co_thread_member_remove(snowflake thread_id, * @throw dpp::length_exception Image data is larger than the maximum size of 256 kilobytes * \memberof dpp::cluster */ -awaitable co_current_user_edit(const std::string &nickname, const std::string& image_blob = "", const image_type type = i_png); +[[nodiscard]] async co_current_user_edit(const std::string &nickname, const std::string& image_blob = "", const image_type type = i_png); /** * @brief Get current (bot) application @@ -2085,7 +2085,7 @@ awaitable co_current_user_edit(const std::string &nickn * @return application returned object on completion * \memberof dpp::cluster */ -awaitable co_current_application_get(); +[[nodiscard]] async co_current_application_get(); /** * @brief Get current (bot) user @@ -2097,7 +2097,7 @@ awaitable co_current_application_get(); * If you do not have these scopes, these fields are empty. You can safely convert a user_identified to user with `dynamic_cast`. * \memberof dpp::cluster */ -awaitable co_current_user_get(); +[[nodiscard]] async co_current_user_get(); /** * @brief Set the bot's voice state on a stage channel @@ -2122,7 +2122,7 @@ awaitable co_current_user_get(); * @throw std::logic_exception You attempted to set a request_to_speak_timestamp in the past which is not the value of 0. * \memberof dpp::cluster */ -awaitable co_current_user_set_voice_state(snowflake guild_id, snowflake channel_id, bool suppress = false, time_t request_to_speak_timestamp = 0); +[[nodiscard]] async co_current_user_set_voice_state(snowflake guild_id, snowflake channel_id, bool suppress = false, time_t request_to_speak_timestamp = 0); /** * @brief Set a user's voice state on a stage channel @@ -2146,7 +2146,7 @@ awaitable co_current_user_set_voice_state(snowflake gui * @param suppress True if the user's audio should be suppressed, false if it should not * \memberof dpp::cluster */ -awaitable co_user_set_voice_state(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress = false); +[[nodiscard]] async co_user_set_voice_state(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress = false); /** * @brief Get current user's connections (linked accounts, e.g. steam, xbox). @@ -2157,7 +2157,7 @@ awaitable co_user_set_voice_state(snowflake user_id, sn * @return connection_map returned object on completion * \memberof dpp::cluster */ -awaitable co_current_user_connections_get(); +[[nodiscard]] async co_current_user_connections_get(); /** * @brief Get current (bot) user guilds @@ -2166,7 +2166,7 @@ awaitable co_current_user_connections_get(); * @return guild_map returned object on completion * \memberof dpp::cluster */ -awaitable co_current_user_get_guilds(); +[[nodiscard]] async co_current_user_get_guilds(); /** * @brief Leave a guild @@ -2176,7 +2176,7 @@ awaitable co_current_user_get_guilds(); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_current_user_leave_guild(snowflake guild_id); +[[nodiscard]] async co_current_user_leave_guild(snowflake guild_id); /** * @brief Get a user by id, without using the cache @@ -2191,7 +2191,7 @@ awaitable co_current_user_leave_guild(snowflake guild_i * Call `dpp::find_user` instead that looks up the user in the cache rather than a REST call. * \memberof dpp::cluster */ -awaitable co_user_get(snowflake user_id); +[[nodiscard]] async co_user_get(snowflake user_id); /** * @brief Get a user by id, checking in the cache first @@ -2206,7 +2206,7 @@ awaitable co_user_get(snowflake user_id); * where you want to for example resolve a user who may no longer be in the bot's guilds, for something like a ban log message. * \memberof dpp::cluster */ -awaitable co_user_get_cached(snowflake user_id); +[[nodiscard]] async co_user_get_cached(snowflake user_id); /** * @brief Get all voice regions @@ -2215,7 +2215,7 @@ awaitable co_user_get_cached(snowflake user_id); * @return voiceregion_map returned object on completion * \memberof dpp::cluster */ -awaitable co_get_voice_regions(); +[[nodiscard]] async co_get_voice_regions(); /** * @brief Get guild voice regions. @@ -2230,7 +2230,7 @@ awaitable co_get_voice_regions(); * @return voiceregion_map returned object on completion * \memberof dpp::cluster */ -awaitable co_guild_get_voice_regions(snowflake guild_id); +[[nodiscard]] async co_guild_get_voice_regions(snowflake guild_id); /** * @brief Create a webhook @@ -2241,7 +2241,7 @@ awaitable co_guild_get_voice_regions(snowflake guild_id * @return webhook returned object on completion * \memberof dpp::cluster */ -awaitable co_create_webhook(const class webhook &w); +[[nodiscard]] async co_create_webhook(const class webhook &w); /** * @brief Delete a webhook @@ -2252,7 +2252,7 @@ awaitable co_create_webhook(const class webhook &w); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_delete_webhook(snowflake webhook_id); +[[nodiscard]] async co_delete_webhook(snowflake webhook_id); /** * @brief Delete webhook message @@ -2265,7 +2265,7 @@ awaitable co_delete_webhook(snowflake webhook_id); * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_delete_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); +[[nodiscard]] async co_delete_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); /** * @brief Delete webhook with token @@ -2276,7 +2276,7 @@ awaitable co_delete_webhook_message(const class webhook * @return confirmation returned object on completion * \memberof dpp::cluster */ -awaitable co_delete_webhook_with_token(snowflake webhook_id, const std::string &token); +[[nodiscard]] async co_delete_webhook_with_token(snowflake webhook_id, const std::string &token); /** * @brief Edit webhook @@ -2287,7 +2287,7 @@ awaitable co_delete_webhook_with_token(snowflake webhoo * @return webhook returned object on completion * \memberof dpp::cluster */ -awaitable co_edit_webhook(const class webhook& wh); +[[nodiscard]] async co_edit_webhook(const class webhook& wh); /** * @brief Edit webhook message @@ -2306,7 +2306,7 @@ awaitable co_edit_webhook(const class webhook& wh); * @return message returned object on completion * \memberof dpp::cluster */ -awaitable co_edit_webhook_message(const class webhook &wh, const struct message &m, snowflake thread_id = 0); +[[nodiscard]] async co_edit_webhook_message(const class webhook &wh, const struct message &m, snowflake thread_id = 0); /** * @brief Edit webhook with token (token is encapsulated in the webhook object) @@ -2316,7 +2316,7 @@ awaitable co_edit_webhook_message(const class webhook & * @return webhook returned object on completion * \memberof dpp::cluster */ -awaitable co_edit_webhook_with_token(const class webhook& wh); +[[nodiscard]] async co_edit_webhook_with_token(const class webhook& wh); /** * @brief Execute webhook @@ -2332,7 +2332,7 @@ awaitable co_edit_webhook_with_token(const class webhoo * @note If the webhook channel is a forum channel, you must provide either `thread_id` or `thread_name`. If `thread_id` is provided, the message will send in that thread. If `thread_name` is provided, a thread with that name will be created in the forum channel. * \memberof dpp::cluster */ -awaitable co_execute_webhook(const class webhook &wh, const struct message &m, bool wait = false, snowflake thread_id = 0, const std::string& thread_name = ""); +[[nodiscard]] async co_execute_webhook(const class webhook &wh, const struct message &m, bool wait = false, snowflake thread_id = 0, const std::string& thread_name = ""); /** * @brief Get channel webhooks @@ -2342,7 +2342,7 @@ awaitable co_execute_webhook(const class webhook &wh, c * @return webhook_map returned object on completion * \memberof dpp::cluster */ -awaitable co_get_channel_webhooks(snowflake channel_id); +[[nodiscard]] async co_get_channel_webhooks(snowflake channel_id); /** * @brief Get guild webhooks @@ -2352,7 +2352,7 @@ awaitable co_get_channel_webhooks(snowflake channel_id) * @return webhook_map returned object on completion * \memberof dpp::cluster */ -awaitable co_get_guild_webhooks(snowflake guild_id); +[[nodiscard]] async co_get_guild_webhooks(snowflake guild_id); /** * @brief Get webhook @@ -2362,7 +2362,7 @@ awaitable co_get_guild_webhooks(snowflake guild_id); * @return webhook returned object on completion * \memberof dpp::cluster */ -awaitable co_get_webhook(snowflake webhook_id); +[[nodiscard]] async co_get_webhook(snowflake webhook_id); /** * @brief Get webhook message @@ -2375,7 +2375,7 @@ awaitable co_get_webhook(snowflake webhook_id); * @return message returned object on completion * \memberof dpp::cluster */ -awaitable co_get_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); +[[nodiscard]] async co_get_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); /** * @brief Get webhook using token @@ -2386,9 +2386,9 @@ awaitable co_get_webhook_message(const class webhook &w * @return webhook returned object on completion * \memberof dpp::cluster */ -awaitable co_get_webhook_with_token(snowflake webhook_id, const std::string &token); +[[nodiscard]] async co_get_webhook_with_token(snowflake webhook_id, const std::string &token); /* End of auto-generated definitions */ -awaitable co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", std::multimap headers = {}); +[[nodiscard]] async co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}); diff --git a/include/dpp/coro/async.h b/include/dpp/coro/async.h index 2137f89dce..7b4e3ac5da 100644 --- a/include/dpp/coro/async.h +++ b/include/dpp/coro/async.h @@ -28,115 +28,125 @@ #include #include #include +#include +#include namespace dpp { -struct confirmation_callback_t; +namespace detail { /** - * @brief A co_await-able object handling an API call in parallel with the caller. - * - * This class is the return type of the dpp::cluster::co_* methods, but it can also be created manually to wrap any async call. + * @brief Empty struct used for overload resolution. + */ +struct empty_tag_t{}; + +/** + * @brief Represents the step an std::async is at. + */ +enum class async_state_t { + sent, /* Request was sent but not co_await-ed. handle is nullptr, result_storage is not constructed */ + waiting, /* Request was co_await-ed. handle is valid, result_storage is not constructed */ + done, /* Request was completed. handle is unknown, result_storage is valid */ + dangling /* Request was never co_await-ed. */ +}; + +/** + * @brief State of the async and its callback. + * + * Defined outside of dpp::async because this seems to work better with Intellisense. + */ +template +struct async_callback_data { + /** + * @brief Number of references to this callback state. + */ + std::atomic ref_count{1}; + + /** + * @brief State of the awaitable and the API callback + */ + std::atomic state = detail::async_state_t::sent; + + /** + * @brief The stored result of the API call, stored as an array of bytes to directly construct in place + */ + alignas(R) std::array result_storage; + + /** + * @brief Handle to the coroutine co_await-ing on this API call + * + * @see std::coroutine_handle + */ + std_coroutine::coroutine_handle<> coro_handle = nullptr; + + /** + * @brief Convenience function to construct the result in the storage and initialize its lifetime + * + * @warning This is only a convenience function, ONLY CALL THIS IN THE CALLBACK, before setting state to done. + */ + template + void construct_result(Ts&&... ts) { + // Standard-compliant type punning yay + std::construct_at(reinterpret_cast(result_storage.data()), std::forward(ts)...); + } + + /** + * @brief Destructor. + * + * Also destroys the result if present. + */ + ~async_callback_data() { + if (state.load() == detail::async_state_t::done) { + std::destroy_at(reinterpret_cast(result_storage.data())); + } + } +}; + +/** + * @brief Base class of dpp::async. This class should not be used directly by a user, use dpp::async instead. * - * @remark - This object's methods, other than constructors and operators, should not be called directly. It is designed to be used with coroutine keywords such as co_await. - * @remark - This object must not be co_await-ed more than once. - * @remark - The coroutine may be resumed in another thread, do not rely on thread_local variables. - * @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. - * @tparam R The return type of the API call. Defaults to confirmation_callback_t + * @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::async so a user cannot call await_suspend and await_resume directly. */ template -class async { +class async_base { /** * @brief Ref-counted callback, contains the callback logic and manages the lifetime of the callback data over multiple threads. */ struct shared_callback { /** - * @brief State of the async and its callback. + * @brief Self-managed ref-counted pointer to the state data */ - struct callback_state { - enum state_t { - waiting, - done, - dangling - }; - - /** - * @brief Mutex to ensure the API result isn't set at the same time the coroutine is awaited and its value is checked, or the async is destroyed - */ - std::mutex mutex{}; - - /** - * @brief Number of references to this callback state. - */ - int ref_count; - - /** - * @brief State of the awaitable and the API callback - */ - state_t state = waiting; - - /** - * @brief The stored result of the API call - */ - std::optional result = std::nullopt; - - /** - * @brief Handle to the coroutine co_await-ing on this API call - * - * @see std::coroutine_handle - */ - detail::std_coroutine::coroutine_handle<> coro_handle = nullptr; - }; - - callback_state *state; + detail::async_callback_data *state = new detail::async_callback_data; /** * @brief Callback function. * + * Constructs the callback data, and if the coroutine was awaiting, resume it * @param cback The result of the API call. + * @tparam V Forwarding reference convertible to R */ - void operator()(const R &cback) const { - std::unique_lock lock{get_mutex()}; - - if (state->state == callback_state::dangling) // Async object is gone - likely an exception killed it or it was never co_await-ed - return; - state->result = cback; - state->state = callback_state::done; - if (state->coro_handle) { - auto handle = state->coro_handle; - state->coro_handle = nullptr; - lock.unlock(); - handle.resume(); + template V> + void operator()(V &&cback) const { + state->construct_result(std::forward(cback)); + if (auto previous_state = state->state.exchange(detail::async_state_t::done); previous_state == detail::async_state_t::waiting) { + state->coro_handle.resume(); } } /** * @brief Main constructor, allocates a new callback_state object. */ - shared_callback() : state{new callback_state{.ref_count = 1}} {} + shared_callback() = default; /** - * @brief Destructor. Releases the held reference and destroys if no other references exist. + * @brief Empty constructor, holds no state. */ - ~shared_callback() { - if (!state) // Moved-from object - return; - - std::unique_lock lock{state->mutex}; - - if (state->ref_count) { - --(state->ref_count); - if (state->ref_count <= 0) {; - lock.unlock(); - delete state; - } - } - } + explicit shared_callback(detail::empty_tag_t) noexcept : state{nullptr} {} /** * @brief Copy constructor. Takes shared ownership of the callback state, increasing the reference count. */ - shared_callback(const shared_callback &other) { + shared_callback(const shared_callback &other) noexcept { this->operator=(other); } @@ -147,12 +157,23 @@ class async { this->operator=(std::move(other)); } + /** + * @brief Destructor. Releases the held reference and destroys if no other references exist. + */ + ~shared_callback() { + if (!state) // Moved-from object + return; + + auto count = state->ref_count.fetch_sub(1); + if (count == 0) { + delete state; + } + } + /** * @brief Copy assignment. Takes shared ownership of the callback state, increasing the reference count. */ shared_callback &operator=(const shared_callback &other) noexcept { - std::lock_guard lock{other.get_mutex()}; - state = other.state; ++state->ref_count; return *this; @@ -162,8 +183,6 @@ class async { * @brief Move assignment. Transfers ownership from another object, leaving intact the reference count. The other object releases the callback state. */ shared_callback &operator=(shared_callback &&other) noexcept { - std::lock_guard lock{other.get_mutex()}; - state = std::exchange(other.state, nullptr); return *this; } @@ -171,34 +190,41 @@ class async { /** * @brief Function called by the async when it is destroyed when it was never co_awaited, signals to the callback to abort. */ - void set_dangling() { + void set_dangling() noexcept { if (!state) // moved-from object return; - std::lock_guard lock{get_mutex()}; + state->state.store(detail::async_state_t::dangling); + } - if (state->state == callback_state::waiting) - state->state = callback_state::dangling; + bool done(std::memory_order order = std::memory_order_seq_cst) const noexcept { + return (state->state.load(order) == detail::async_state_t::done); } /** - * @brief Convenience function to get the shared callback state's mutex. + * @brief Convenience function to get the shared callback state's result. + * + * @warning It is UB to call this on a callback whose state is anything else but async_state_t::done. */ - std::mutex &get_mutex() const { - return (state->mutex); + R &get_result() noexcept { + assert(state && done()); + return (*reinterpret_cast(state->result_storage.data())); } /** * @brief Convenience function to get the shared callback state's result. + * + * @warning It is UB to call this on a callback whose state is anything else but async_state_t::done. */ - std::optional &get_result() const { - return (state->result); + const R &get_result() const noexcept { + assert(state && done()); + return (*reinterpret_cast(state->result_storage.data())); } }; /** * @brief Shared state of the async and its callback, to be used across threads. */ - shared_callback api_callback; + shared_callback api_callback{nullptr}; public: /** @@ -212,7 +238,7 @@ class async { #ifndef _DOXYGEN_ requires std::invocable> #endif - async(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} { + explicit async_base(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} { std::invoke(std::forward(fun), std::forward(obj), std::forward(args)..., api_callback); } @@ -226,30 +252,26 @@ class async { #ifndef _DOXYGEN_ requires std::invocable> #endif - async(Fun &&fun, Args&&... args) : api_callback{} { + explicit async_base(Fun &&fun, Args&&... args) : api_callback{} { std::invoke(std::forward(fun), std::forward(args)..., api_callback); } /** - * @brief Construct an async wrapping an awaitable, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. - * - * @param callable The awaitable object whose API call to execute. + * @brief Construct an empty async. Using `co_await` on an empty async is undefined behavior. */ - async(const awaitable &awaitable) : api_callback{} { - std::invoke(awaitable.request, api_callback); - } + async_base() noexcept : api_callback{detail::empty_tag_t{}} {} /** * @brief Destructor. If any callback is pending it will be aborted. */ - ~async() { + ~async_base() { api_callback.set_dangling(); } /** * @brief Copy constructor is disabled */ - async(const async &) = delete; + async_base(const async_base &) = delete; /** * @brief Move constructor @@ -259,12 +281,12 @@ class async { * @remark Using the moved-from async after this function is undefined behavior. * @param other The async object to move the data from. */ - async(async &&other) noexcept = default; + async_base(async_base &&other) noexcept = default; /** * @brief Copy assignment is disabled */ - async &operator=(const async &) = delete; + async_base &operator=(const async_base &) = delete; /** * @brief Move assignment operator. @@ -274,7 +296,7 @@ class async { * @remark Using the moved-from async after this function is undefined behavior. * @param other The async object to move the data from */ - async &operator=(async &&other) noexcept = default; + async_base &operator=(async_base &&other) noexcept = default; /** * @brief First function called by the standard library when the object is co-awaited. @@ -284,10 +306,8 @@ class async { * @remark Do not call this manually, use the co_await keyword instead. * @return bool Whether we already have the result of the API call or not */ - bool await_ready() noexcept { - std::lock_guard lock{api_callback.get_mutex()}; - - return api_callback.get_result().has_value(); + bool await_ready() const noexcept { + return api_callback.done(); } /** @@ -298,27 +318,119 @@ class async { * @remark Do not call this manually, use the co_await keyword instead. * @param handle The handle to the coroutine co_await-ing and being suspended */ - template - bool await_suspend(detail::std_coroutine::coroutine_handle caller) { - std::lock_guard lock{api_callback.get_mutex()}; - - if (api_callback.get_result().has_value()) - return false; // immediately resume the coroutine as we already have the result of the api call - if constexpr (requires (T t) { t.is_sync = false; }) - caller.promise().is_sync = false; + bool await_suspend(detail::std_coroutine::coroutine_handle<> caller) noexcept { + auto sent = detail::async_state_t::sent; api_callback.state->coro_handle = caller; - return true; // suspend the caller, the callback will resume it + return api_callback.state->state.compare_exchange_strong(sent, detail::async_state_t::waiting); // true (suspend) if `sent` was replaced with `waiting` -- false (resume) if the value was not `sent` (`done` is the only other option) } /** * @brief Function called by the standard library when the async is resumed. Its return value is what the whole co_await expression evaluates to * * @remark Do not call this manually, use the co_await keyword instead. - * @return R The result of the API call. + * @return R& The result of the API call as an lvalue reference. + */ + R& await_resume() & noexcept { + return api_callback.get_result(); + } + + + /** + * @brief Function called by the standard library when the async is resumed. Its return value is what the whole co_await expression evaluates to + * + * @remark Do not call this manually, use the co_await keyword instead. + * @return const R& The result of the API call as a const lvalue reference. + */ + const R& await_resume() const& noexcept { + return api_callback.get_result(); + } + + /** + * @brief Function called by the standard library when the async is resumed. Its return value is what the whole co_await expression evaluates to + * + * @remark Do not call this manually, use the co_await keyword instead. + * @return R&& The result of the API call as an rvalue reference. + */ + R&& await_resume() && noexcept { + return std::move(api_callback.get_result()); + } +}; + +} // namespace detail + +struct confirmation_callback_t; + +/** + * @brief A co_await-able object handling an API call in parallel with the caller. + * + * This class is the return type of the dpp::cluster::co_* methods, but it can also be created manually to wrap any async call. + * + * @remark - The coroutine may be resumed in another thread, do not rely on thread_local variables. + * @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. + * @tparam R The return type of the API call. Defaults to confirmation_callback_t + */ +template +class async : private detail::async_base { + /** + * @brief Base class has friend access for CRTP downcast + */ + friend class detail::async_base; + +public: + using detail::async_base::async_base; // use async_base's constructors. unfortunately on clang this doesn't include the templated ones so we have to delegate below + using detail::async_base::operator=; // use async_base's assignment operator + using detail::async_base::await_ready; // expose await_ready as public + + /** + * @brief Construct an async object wrapping an object method, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. + * + * @param obj The object to call the method on + * @param fun The method of the object to call. Its last parameter must be a callback taking a parameter of type R + * @param args Parameters to pass to the method, excluding the callback + */ + template +#ifndef _DOXYGEN_ + requires std::invocable> +#endif + explicit async(Obj &&obj, Fun &&fun, Args&&... args) : detail::async_base{std::forward(obj), std::forward(fun), std::forward(args)...} {} + + /** + * @brief Construct an async object wrapping an invokeable object, the call is made immediately by forwarding to std::invoke and can be awaited later to retrieve the result. + * + * @param fun The object to call using std::invoke. Its last parameter must be a callable taking a parameter of type R + * @param args Parameters to pass to the object, excluding the callback + */ + template +#ifndef _DOXYGEN_ + requires std::invocable> +#endif + explicit async(Fun &&fun, Args&&... args) : detail::async_base{std::forward(fun), std::forward(args)...} {} + + /** + * @brief Suspend the caller until the request completes. + * + * @return R& On resumption, this expression evaluates to the result object of type R, as a reference. + */ + auto& operator co_await() & noexcept { + return static_cast&>(*this); + } + + /** + * @brief Suspend the caller until the request completes. + * + * @return const R& On resumption, this expression evaluates to the result object of type R, as a const reference. + */ + const auto& operator co_await() const & noexcept { + return static_cast const&>(*this); + } + + /** + * @brief Suspend the caller until the request completes. + * + * @return R&& On resumption, this expression evaluates to the result object of type R, as an rvalue reference. */ - R await_resume() { - // no locking needed here as the callback has already executed - return std::move(*api_callback.get_result()); + auto&& operator co_await() && noexcept { + return static_cast&&>(*this); } }; diff --git a/include/dpp/dispatcher.h b/include/dpp/dispatcher.h index 42b206633c..4a6c1831e8 100644 --- a/include/dpp/dispatcher.h +++ b/include/dpp/dispatcher.h @@ -486,7 +486,7 @@ struct DPP_EXPORT interaction_create_t : public event_dispatch_t { * * On success the result will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_reply() const; + dpp::async co_reply() const; /** * @brief Send a reply for this interaction @@ -495,7 +495,7 @@ struct DPP_EXPORT interaction_create_t : public event_dispatch_t { * @param m Message object to send. Not all fields are supported by Discord. * On success the result will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_reply(interaction_response_type t, const message & m) const; + dpp::async co_reply(interaction_response_type t, const message & m) const; /** * @brief Send a reply for this interaction @@ -504,7 +504,7 @@ struct DPP_EXPORT interaction_create_t : public event_dispatch_t { * @param mt The string value to send, for simple text only messages * On success the result will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_reply(interaction_response_type t, const std::string & mt) const; + dpp::async co_reply(interaction_response_type t, const std::string & mt) const; /** * @brief Send a reply for this interaction. @@ -513,7 +513,7 @@ struct DPP_EXPORT interaction_create_t : public event_dispatch_t { * @param m Message object to send. Not all fields are supported by Discord. * On success the result will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_reply(const message & m) const; + dpp::async co_reply(const message & m) const; /** * @brief Send a reply for this interaction. @@ -522,7 +522,7 @@ struct DPP_EXPORT interaction_create_t : public event_dispatch_t { * @param mt The string value to send, for simple text only messages * On success the result will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_reply(const std::string & mt) const; + dpp::async co_reply(const std::string & mt) const; /** * @brief Reply to interaction with a dialog box @@ -530,7 +530,7 @@ struct DPP_EXPORT interaction_create_t : public event_dispatch_t { * @param mr Dialog box response to send * On success the result will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_dialog(const interaction_modal_response& mr) const; + dpp::async co_dialog(const interaction_modal_response& mr) const; /** * @brief Edit the response for this interaction @@ -538,7 +538,7 @@ struct DPP_EXPORT interaction_create_t : public event_dispatch_t { * @param m Message object to send. Not all fields are supported by Discord. * On success the result will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_edit_response(const message & m) const; + dpp::async co_edit_response(const message & m) const; /** * @brief Edit the response for this interaction @@ -546,7 +546,7 @@ struct DPP_EXPORT interaction_create_t : public event_dispatch_t { * @param mt The string value to send, for simple text only messages * On success the result will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_edit_response(const std::string & mt) const; + dpp::async co_edit_response(const std::string & mt) const; /** * @brief Set the bot to 'thinking' state where you have up to 15 minutes to respond @@ -554,14 +554,14 @@ struct DPP_EXPORT interaction_create_t : public event_dispatch_t { * @param ephemeral True if the thinking state should be ephemeral * On success the result will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_thinking(bool ephemeral = false) const; + dpp::async co_thinking(bool ephemeral = false) const; /** * @brief Get original response message for this interaction * * On success the result will contain a dpp::message object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_get_original_response() const; + dpp::async co_get_original_response() const; /** * @brief Edit original response message for this interaction @@ -569,14 +569,14 @@ struct DPP_EXPORT interaction_create_t : public event_dispatch_t { * @param m Message object to send. Not all fields are supported by Discord. * On success the result will contain a dpp::message object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_edit_original_response(const message & m) const; + dpp::async co_edit_original_response(const message & m) const; /** * @brief Delete original response message for this interaction. This cannot be used on an ephemeral interaction response. * * On success the result will contain a dpp::confirmation object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ - dpp::awaitable co_delete_original_response() const; + dpp::async co_delete_original_response() const; #endif /* DPP_CORO */ /** diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index 3f92d61e52..94099c7a9e 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -105,9 +105,9 @@ void cluster::tick_timers() { } #ifdef DPP_CORO -awaitable cluster::co_sleep(uint64_t seconds) { - return {[this, seconds] (auto &&cb) { - start_timer([this, cb](dpp::timer handle) { +async cluster::co_sleep(uint64_t seconds) { + return async{[this, seconds] (auto &&cb) mutable { + start_timer([this, cb] (dpp::timer handle) { cb(handle); stop_timer(handle); }, seconds); diff --git a/src/dpp/cluster_coro_calls.cpp b/src/dpp/cluster_coro_calls.cpp index ad23ec92c7..5dbdf9ef00 100644 --- a/src/dpp/cluster_coro_calls.cpp +++ b/src/dpp/cluster_coro_calls.cpp @@ -35,764 +35,764 @@ namespace dpp { -awaitable cluster::co_global_bulk_command_create(const std::vector &commands) { - return [=, this] (auto &&cc) { this->global_bulk_command_create(commands, cc); }; +async cluster::co_global_bulk_command_create(const std::vector &commands) { + return async{ this, static_cast &, command_completion_event_t)>(&cluster::global_bulk_command_create), commands }; } -awaitable cluster::co_global_command_create(const slashcommand &s) { - return [=, this] (auto &&cc) { this->global_command_create(s, cc); }; +async cluster::co_global_command_create(const slashcommand &s) { + return async{ this, static_cast(&cluster::global_command_create), s }; } -awaitable cluster::co_global_command_get(snowflake id) { - return [=, this] (auto &&cc) { this->global_command_get(id, cc); }; +async cluster::co_global_command_get(snowflake id) { + return async{ this, static_cast(&cluster::global_command_get), id }; } -awaitable cluster::co_global_command_delete(snowflake id) { - return [=, this] (auto &&cc) { this->global_command_delete(id, cc); }; +async cluster::co_global_command_delete(snowflake id) { + return async{ this, static_cast(&cluster::global_command_delete), id }; } -awaitable cluster::co_global_command_edit(const slashcommand &s) { - return [=, this] (auto &&cc) { this->global_command_edit(s, cc); }; +async cluster::co_global_command_edit(const slashcommand &s) { + return async{ this, static_cast(&cluster::global_command_edit), s }; } -awaitable cluster::co_global_commands_get() { - return [=, this] (auto &&cc) { this->global_commands_get(cc); }; +async cluster::co_global_commands_get() { + return async{ this, static_cast(&cluster::global_commands_get) }; } -awaitable cluster::co_guild_bulk_command_create(const std::vector &commands, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_bulk_command_create(commands, guild_id, cc); }; +async cluster::co_guild_bulk_command_create(const std::vector &commands, snowflake guild_id) { + return async{ this, static_cast &, snowflake, command_completion_event_t)>(&cluster::guild_bulk_command_create), commands, guild_id }; } -awaitable cluster::co_guild_commands_get_permissions(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_commands_get_permissions(guild_id, cc); }; +async cluster::co_guild_commands_get_permissions(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_commands_get_permissions), guild_id }; } -awaitable cluster::co_guild_bulk_command_edit_permissions(const std::vector &commands, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_bulk_command_edit_permissions(commands, guild_id, cc); }; +async cluster::co_guild_bulk_command_edit_permissions(const std::vector &commands, snowflake guild_id) { + return async{ this, static_cast &, snowflake, command_completion_event_t)>(&cluster::guild_bulk_command_edit_permissions), commands, guild_id }; } -awaitable cluster::co_guild_command_create(const slashcommand &s, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_create(s, guild_id, cc); }; +async cluster::co_guild_command_create(const slashcommand &s, snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_command_create), s, guild_id }; } -awaitable cluster::co_guild_command_delete(snowflake id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_delete(id, guild_id, cc); }; +async cluster::co_guild_command_delete(snowflake id, snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_command_delete), id, guild_id }; } -awaitable cluster::co_guild_command_edit_permissions(const slashcommand &s, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_edit_permissions(s, guild_id, cc); }; +async cluster::co_guild_command_edit_permissions(const slashcommand &s, snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_command_edit_permissions), s, guild_id }; } -awaitable cluster::co_guild_command_get(snowflake id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_get(id, guild_id, cc); }; +async cluster::co_guild_command_get(snowflake id, snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_command_get), id, guild_id }; } -awaitable cluster::co_guild_command_get_permissions(snowflake id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_get_permissions(id, guild_id, cc); }; +async cluster::co_guild_command_get_permissions(snowflake id, snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_command_get_permissions), id, guild_id }; } -awaitable cluster::co_guild_command_edit(const slashcommand &s, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_edit(s, guild_id, cc); }; +async cluster::co_guild_command_edit(const slashcommand &s, snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_command_edit), s, guild_id }; } -awaitable cluster::co_guild_commands_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_commands_get(guild_id, cc); }; +async cluster::co_guild_commands_get(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_commands_get), guild_id }; } -awaitable cluster::co_interaction_response_create(snowflake interaction_id, const std::string &token, const interaction_response &r) { - return [=, this] (auto &&cc) { this->interaction_response_create(interaction_id, token, r, cc); }; +async cluster::co_interaction_response_create(snowflake interaction_id, const std::string &token, const interaction_response &r) { + return async{ this, static_cast(&cluster::interaction_response_create), interaction_id, token, r }; } -awaitable cluster::co_interaction_response_edit(const std::string &token, const message &m) { - return [=, this] (auto &&cc) { this->interaction_response_edit(token, m, cc); }; +async cluster::co_interaction_response_edit(const std::string &token, const message &m) { + return async{ this, static_cast(&cluster::interaction_response_edit), token, m }; } -awaitable cluster::co_interaction_response_get_original(const std::string &token) { - return [=, this] (auto &&cc) { this->interaction_response_get_original(token, cc); }; +async cluster::co_interaction_response_get_original(const std::string &token) { + return async{ this, static_cast(&cluster::interaction_response_get_original), token }; } -awaitable cluster::co_interaction_followup_create(const std::string &token, const message &m) { - return [=, this] (auto &&cc) { this->interaction_followup_create(token, m, cc); }; +async cluster::co_interaction_followup_create(const std::string &token, const message &m) { + return async{ this, static_cast(&cluster::interaction_followup_create), token, m }; } -awaitable cluster::co_interaction_followup_edit_original(const std::string &token, const message &m) { - return [=, this] (auto &&cc) { this->interaction_followup_edit_original(token, m, cc); }; +async cluster::co_interaction_followup_edit_original(const std::string &token, const message &m) { + return async{ this, static_cast(&cluster::interaction_followup_edit_original), token, m }; } -awaitable cluster::co_interaction_followup_delete(const std::string &token) { - return [=, this] (auto &&cc) { this->interaction_followup_delete(token, cc); }; +async cluster::co_interaction_followup_delete(const std::string &token) { + return async{ this, static_cast(&cluster::interaction_followup_delete), token }; } -awaitable cluster::co_interaction_followup_edit(const std::string &token, const message &m) { - return [=, this] (auto &&cc) { this->interaction_followup_edit(token, m, cc); }; +async cluster::co_interaction_followup_edit(const std::string &token, const message &m) { + return async{ this, static_cast(&cluster::interaction_followup_edit), token, m }; } -awaitable cluster::co_interaction_followup_get(const std::string &token, snowflake message_id) { - return [=, this] (auto &&cc) { this->interaction_followup_get(token, message_id, cc); }; +async cluster::co_interaction_followup_get(const std::string &token, snowflake message_id) { + return async{ this, static_cast(&cluster::interaction_followup_get), token, message_id }; } -awaitable cluster::co_interaction_followup_get_original(const std::string &token) { - return [=, this] (auto &&cc) { this->interaction_followup_get_original(token, cc); }; +async cluster::co_interaction_followup_get_original(const std::string &token) { + return async{ this, static_cast(&cluster::interaction_followup_get_original), token }; } -awaitable cluster::co_automod_rules_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->automod_rules_get(guild_id, cc); }; +async cluster::co_automod_rules_get(snowflake guild_id) { + return async{ this, static_cast(&cluster::automod_rules_get), guild_id }; } -awaitable cluster::co_automod_rule_get(snowflake guild_id, snowflake rule_id) { - return [=, this] (auto &&cc) { this->automod_rule_get(guild_id, rule_id, cc); }; +async cluster::co_automod_rule_get(snowflake guild_id, snowflake rule_id) { + return async{ this, static_cast(&cluster::automod_rule_get), guild_id, rule_id }; } -awaitable cluster::co_automod_rule_create(snowflake guild_id, const automod_rule& r) { - return [=, this] (auto &&cc) { this->automod_rule_create(guild_id, r, cc); }; +async cluster::co_automod_rule_create(snowflake guild_id, const automod_rule& r) { + return async{ this, static_cast(&cluster::automod_rule_create), guild_id, r }; } -awaitable cluster::co_automod_rule_edit(snowflake guild_id, const automod_rule& r) { - return [=, this] (auto &&cc) { this->automod_rule_edit(guild_id, r, cc); }; +async cluster::co_automod_rule_edit(snowflake guild_id, const automod_rule& r) { + return async{ this, static_cast(&cluster::automod_rule_edit), guild_id, r }; } -awaitable cluster::co_automod_rule_delete(snowflake guild_id, snowflake rule_id) { - return [=, this] (auto &&cc) { this->automod_rule_delete(guild_id, rule_id, cc); }; +async cluster::co_automod_rule_delete(snowflake guild_id, snowflake rule_id) { + return async{ this, static_cast(&cluster::automod_rule_delete), guild_id, rule_id }; } -awaitable cluster::co_channel_create(const class channel &c) { - return [=, this] (auto &&cc) { this->channel_create(c, cc); }; +async cluster::co_channel_create(const class channel &c) { + return async{ this, static_cast(&cluster::channel_create), c }; } -awaitable cluster::co_channel_delete_permission(const class channel &c, snowflake overwrite_id) { - return [=, this] (auto &&cc) { this->channel_delete_permission(c, overwrite_id, cc); }; +async cluster::co_channel_delete_permission(const class channel &c, snowflake overwrite_id) { + return async{ this, static_cast(&cluster::channel_delete_permission), c, overwrite_id }; } -awaitable cluster::co_channel_delete(snowflake channel_id) { - return [=, this] (auto &&cc) { this->channel_delete(channel_id, cc); }; +async cluster::co_channel_delete(snowflake channel_id) { + return async{ this, static_cast(&cluster::channel_delete), channel_id }; } -awaitable cluster::co_channel_edit_permissions(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { - return [=, this] (auto &&cc) { this->channel_edit_permissions(c, overwrite_id, allow, deny, member, cc); }; +async cluster::co_channel_edit_permissions(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { + return async{ this, static_cast(&cluster::channel_edit_permissions), c, overwrite_id, allow, deny, member }; } -awaitable cluster::co_channel_edit_permissions(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { - return [=, this] (auto &&cc) { this->channel_edit_permissions(channel_id, overwrite_id, allow, deny, member, cc); }; +async cluster::co_channel_edit_permissions(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { + return async{ this, static_cast(&cluster::channel_edit_permissions), channel_id, overwrite_id, allow, deny, member }; } -awaitable cluster::co_channel_edit_positions(const std::vector &c) { - return [=, this] (auto &&cc) { this->channel_edit_positions(c, cc); }; +async cluster::co_channel_edit_positions(const std::vector &c) { + return async{ this, static_cast &, command_completion_event_t)>(&cluster::channel_edit_positions), c }; } -awaitable cluster::co_channel_edit(const class channel &c) { - return [=, this] (auto &&cc) { this->channel_edit(c, cc); }; +async cluster::co_channel_edit(const class channel &c) { + return async{ this, static_cast(&cluster::channel_edit), c }; } -awaitable cluster::co_channel_follow_news(const class channel &c, snowflake target_channel_id) { - return [=, this] (auto &&cc) { this->channel_follow_news(c, target_channel_id, cc); }; +async cluster::co_channel_follow_news(const class channel &c, snowflake target_channel_id) { + return async{ this, static_cast(&cluster::channel_follow_news), c, target_channel_id }; } -awaitable cluster::co_channel_get(snowflake c) { - return [=, this] (auto &&cc) { this->channel_get(c, cc); }; +async cluster::co_channel_get(snowflake c) { + return async{ this, static_cast(&cluster::channel_get), c }; } -awaitable cluster::co_channel_invite_create(const class channel &c, const class invite &i) { - return [=, this] (auto &&cc) { this->channel_invite_create(c, i, cc); }; +async cluster::co_channel_invite_create(const class channel &c, const class invite &i) { + return async{ this, static_cast(&cluster::channel_invite_create), c, i }; } -awaitable cluster::co_channel_invites_get(const class channel &c) { - return [=, this] (auto &&cc) { this->channel_invites_get(c, cc); }; +async cluster::co_channel_invites_get(const class channel &c) { + return async{ this, static_cast(&cluster::channel_invites_get), c }; } -awaitable cluster::co_channel_typing(const class channel &c) { - return [=, this] (auto &&cc) { this->channel_typing(c, cc); }; +async cluster::co_channel_typing(const class channel &c) { + return async{ this, static_cast(&cluster::channel_typing), c }; } -awaitable cluster::co_channel_typing(snowflake cid) { - return [=, this] (auto &&cc) { this->channel_typing(cid, cc); }; +async cluster::co_channel_typing(snowflake cid) { + return async{ this, static_cast(&cluster::channel_typing), cid }; } -awaitable cluster::co_channels_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->channels_get(guild_id, cc); }; +async cluster::co_channels_get(snowflake guild_id) { + return async{ this, static_cast(&cluster::channels_get), guild_id }; } -awaitable cluster::co_create_dm_channel(snowflake user_id) { - return [=, this] (auto &&cc) { this->create_dm_channel(user_id, cc); }; +async cluster::co_create_dm_channel(snowflake user_id) { + return async{ this, static_cast(&cluster::create_dm_channel), user_id }; } -awaitable cluster::co_current_user_get_dms() { - return [=, this] (auto &&cc) { this->current_user_get_dms(cc); }; +async cluster::co_current_user_get_dms() { + return async{ this, static_cast(&cluster::current_user_get_dms) }; } -awaitable cluster::co_direct_message_create(snowflake user_id, const message &m) { - return [=, this] (auto &&cc) { this->direct_message_create(user_id, m, cc); }; +async cluster::co_direct_message_create(snowflake user_id, const message &m) { + return async{ this, static_cast(&cluster::direct_message_create), user_id, m }; } -awaitable cluster::co_gdm_add(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick) { - return [=, this] (auto &&cc) { this->gdm_add(channel_id, user_id, access_token, nick, cc); }; +async cluster::co_gdm_add(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick) { + return async{ this, static_cast(&cluster::gdm_add), channel_id, user_id, access_token, nick }; } -awaitable cluster::co_gdm_remove(snowflake channel_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->gdm_remove(channel_id, user_id, cc); }; +async cluster::co_gdm_remove(snowflake channel_id, snowflake user_id) { + return async{ this, static_cast(&cluster::gdm_remove), channel_id, user_id }; } -awaitable cluster::co_guild_emoji_create(snowflake guild_id, const class emoji& newemoji) { - return [=, this] (auto &&cc) { this->guild_emoji_create(guild_id, newemoji, cc); }; +async cluster::co_guild_emoji_create(snowflake guild_id, const class emoji& newemoji) { + return async{ this, static_cast(&cluster::guild_emoji_create), guild_id, newemoji }; } -awaitable cluster::co_guild_emoji_delete(snowflake guild_id, snowflake emoji_id) { - return [=, this] (auto &&cc) { this->guild_emoji_delete(guild_id, emoji_id, cc); }; +async cluster::co_guild_emoji_delete(snowflake guild_id, snowflake emoji_id) { + return async{ this, static_cast(&cluster::guild_emoji_delete), guild_id, emoji_id }; } -awaitable cluster::co_guild_emoji_edit(snowflake guild_id, const class emoji& newemoji) { - return [=, this] (auto &&cc) { this->guild_emoji_edit(guild_id, newemoji, cc); }; +async cluster::co_guild_emoji_edit(snowflake guild_id, const class emoji& newemoji) { + return async{ this, static_cast(&cluster::guild_emoji_edit), guild_id, newemoji }; } -awaitable cluster::co_guild_emoji_get(snowflake guild_id, snowflake emoji_id) { - return [=, this] (auto &&cc) { this->guild_emoji_get(guild_id, emoji_id, cc); }; +async cluster::co_guild_emoji_get(snowflake guild_id, snowflake emoji_id) { + return async{ this, static_cast(&cluster::guild_emoji_get), guild_id, emoji_id }; } -awaitable cluster::co_guild_emojis_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_emojis_get(guild_id, cc); }; +async cluster::co_guild_emojis_get(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_emojis_get), guild_id }; } -awaitable cluster::co_get_gateway_bot() { - return [=, this] (auto &&cc) { this->get_gateway_bot(cc); }; +async cluster::co_get_gateway_bot() { + return async{ this, static_cast(&cluster::get_gateway_bot) }; } -awaitable cluster::co_guild_current_member_edit(snowflake guild_id, const std::string &nickname) { - return [=, this] (auto &&cc) { this->guild_current_member_edit(guild_id, nickname, cc); }; +async cluster::co_guild_current_member_edit(snowflake guild_id, const std::string &nickname) { + return async{ this, static_cast(&cluster::guild_current_member_edit), guild_id, nickname }; } -awaitable cluster::co_guild_auditlog_get(snowflake guild_id, snowflake user_id, uint32_t action_type, snowflake before, snowflake after, uint32_t limit) { - return [=, this] (auto &&cc) { this->guild_auditlog_get(guild_id, user_id, action_type, before, after, limit, cc); }; +async cluster::co_guild_auditlog_get(snowflake guild_id, snowflake user_id, uint32_t action_type, snowflake before, snowflake after, uint32_t limit) { + return async{ this, static_cast(&cluster::guild_auditlog_get), guild_id, user_id, action_type, before, after, limit }; } -awaitable cluster::co_guild_ban_add(snowflake guild_id, snowflake user_id, uint32_t delete_message_seconds) { - return [=, this] (auto &&cc) { this->guild_ban_add(guild_id, user_id, delete_message_seconds, cc); }; +async cluster::co_guild_ban_add(snowflake guild_id, snowflake user_id, uint32_t delete_message_seconds) { + return async{ this, static_cast(&cluster::guild_ban_add), guild_id, user_id, delete_message_seconds }; } -awaitable cluster::co_guild_ban_delete(snowflake guild_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_ban_delete(guild_id, user_id, cc); }; +async cluster::co_guild_ban_delete(snowflake guild_id, snowflake user_id) { + return async{ this, static_cast(&cluster::guild_ban_delete), guild_id, user_id }; } -awaitable cluster::co_guild_create(const class guild &g) { - return [=, this] (auto &&cc) { this->guild_create(g, cc); }; +async cluster::co_guild_create(const class guild &g) { + return async{ this, static_cast(&cluster::guild_create), g }; } -awaitable cluster::co_guild_delete(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_delete(guild_id, cc); }; +async cluster::co_guild_delete(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_delete), guild_id }; } -awaitable cluster::co_guild_delete_integration(snowflake guild_id, snowflake integration_id) { - return [=, this] (auto &&cc) { this->guild_delete_integration(guild_id, integration_id, cc); }; +async cluster::co_guild_delete_integration(snowflake guild_id, snowflake integration_id) { + return async{ this, static_cast(&cluster::guild_delete_integration), guild_id, integration_id }; } -awaitable cluster::co_guild_edit(const class guild &g) { - return [=, this] (auto &&cc) { this->guild_edit(g, cc); }; +async cluster::co_guild_edit(const class guild &g) { + return async{ this, static_cast(&cluster::guild_edit), g }; } -awaitable cluster::co_guild_edit_widget(snowflake guild_id, const class guild_widget &gw) { - return [=, this] (auto &&cc) { this->guild_edit_widget(guild_id, gw, cc); }; +async cluster::co_guild_edit_widget(snowflake guild_id, const class guild_widget &gw) { + return async{ this, static_cast(&cluster::guild_edit_widget), guild_id, gw }; } -awaitable cluster::co_guild_get_ban(snowflake guild_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_get_ban(guild_id, user_id, cc); }; +async cluster::co_guild_get_ban(snowflake guild_id, snowflake user_id) { + return async{ this, static_cast(&cluster::guild_get_ban), guild_id, user_id }; } -awaitable cluster::co_guild_get_bans(snowflake guild_id, snowflake before, snowflake after, snowflake limit) { - return [=, this] (auto &&cc) { this->guild_get_bans(guild_id, before, after, limit, cc); }; +async cluster::co_guild_get_bans(snowflake guild_id, snowflake before, snowflake after, snowflake limit) { + return async{ this, static_cast(&cluster::guild_get_bans), guild_id, before, after, limit }; } -awaitable cluster::co_guild_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get(guild_id, cc); }; +async cluster::co_guild_get(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_get), guild_id }; } -awaitable cluster::co_guild_get_integrations(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_integrations(guild_id, cc); }; +async cluster::co_guild_get_integrations(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_get_integrations), guild_id }; } -awaitable cluster::co_guild_get_preview(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_preview(guild_id, cc); }; +async cluster::co_guild_get_preview(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_get_preview), guild_id }; } -awaitable cluster::co_guild_get_vanity(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_vanity(guild_id, cc); }; +async cluster::co_guild_get_vanity(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_get_vanity), guild_id }; } -awaitable cluster::co_guild_get_widget(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_widget(guild_id, cc); }; +async cluster::co_guild_get_widget(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_get_widget), guild_id }; } -awaitable cluster::co_guild_modify_integration(snowflake guild_id, const class integration &i) { - return [=, this] (auto &&cc) { this->guild_modify_integration(guild_id, i, cc); }; +async cluster::co_guild_modify_integration(snowflake guild_id, const class integration &i) { + return async{ this, static_cast(&cluster::guild_modify_integration), guild_id, i }; } -awaitable cluster::co_guild_get_prune_counts(snowflake guild_id, const struct prune& pruneinfo) { - return [=, this] (auto &&cc) { this->guild_get_prune_counts(guild_id, pruneinfo, cc); }; +async cluster::co_guild_get_prune_counts(snowflake guild_id, const struct prune& pruneinfo) { + return async{ this, static_cast(&cluster::guild_get_prune_counts), guild_id, pruneinfo }; } -awaitable cluster::co_guild_begin_prune(snowflake guild_id, const struct prune& pruneinfo) { - return [=, this] (auto &&cc) { this->guild_begin_prune(guild_id, pruneinfo, cc); }; +async cluster::co_guild_begin_prune(snowflake guild_id, const struct prune& pruneinfo) { + return async{ this, static_cast(&cluster::guild_begin_prune), guild_id, pruneinfo }; } -awaitable cluster::co_guild_set_nickname(snowflake guild_id, const std::string &nickname) { - return [=, this] (auto &&cc) { this->guild_set_nickname(guild_id, nickname, cc); }; +async cluster::co_guild_set_nickname(snowflake guild_id, const std::string &nickname) { + return async{ this, static_cast(&cluster::guild_set_nickname), guild_id, nickname }; } -awaitable cluster::co_guild_sync_integration(snowflake guild_id, snowflake integration_id) { - return [=, this] (auto &&cc) { this->guild_sync_integration(guild_id, integration_id, cc); }; +async cluster::co_guild_sync_integration(snowflake guild_id, snowflake integration_id) { + return async{ this, static_cast(&cluster::guild_sync_integration), guild_id, integration_id }; } -awaitable cluster::co_guild_get_onboarding(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_onboarding(guild_id, cc); }; +async cluster::co_guild_get_onboarding(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_get_onboarding), guild_id }; } -awaitable cluster::co_guild_edit_onboarding(const struct onboarding& o) { - return [=, this] (auto &&cc) { this->guild_edit_onboarding(o, cc); }; +async cluster::co_guild_edit_onboarding(const struct onboarding& o) { + return async{ this, static_cast(&cluster::guild_edit_onboarding), o }; } -awaitable cluster::co_guild_get_welcome_screen(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_welcome_screen(guild_id, cc); }; +async cluster::co_guild_get_welcome_screen(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_get_welcome_screen), guild_id }; } -awaitable cluster::co_guild_edit_welcome_screen(snowflake guild_id, const struct welcome_screen& welcome_screen, bool enabled) { - return [=, this] (auto &&cc) { this->guild_edit_welcome_screen(guild_id, welcome_screen, enabled, cc); }; +async cluster::co_guild_edit_welcome_screen(snowflake guild_id, const struct welcome_screen& welcome_screen, bool enabled) { + return async{ this, static_cast(&cluster::guild_edit_welcome_screen), guild_id, welcome_screen, enabled }; } -awaitable cluster::co_guild_add_member(const guild_member& gm, const std::string &access_token) { - return [=, this] (auto &&cc) { this->guild_add_member(gm, access_token, cc); }; +async cluster::co_guild_add_member(const guild_member& gm, const std::string &access_token) { + return async{ this, static_cast(&cluster::guild_add_member), gm, access_token }; } -awaitable cluster::co_guild_edit_member(const guild_member& gm) { - return [=, this] (auto &&cc) { this->guild_edit_member(gm, cc); }; +async cluster::co_guild_edit_member(const guild_member& gm) { + return async{ this, static_cast(&cluster::guild_edit_member), gm }; } -awaitable cluster::co_guild_get_member(snowflake guild_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_get_member(guild_id, user_id, cc); }; +async cluster::co_guild_get_member(snowflake guild_id, snowflake user_id) { + return async{ this, static_cast(&cluster::guild_get_member), guild_id, user_id }; } -awaitable cluster::co_guild_get_members(snowflake guild_id, uint16_t limit, snowflake after) { - return [=, this] (auto &&cc) { this->guild_get_members(guild_id, limit, after, cc); }; +async cluster::co_guild_get_members(snowflake guild_id, uint16_t limit, snowflake after) { + return async{ this, static_cast(&cluster::guild_get_members), guild_id, limit, after }; } -awaitable cluster::co_guild_member_add_role(snowflake guild_id, snowflake user_id, snowflake role_id) { - return [=, this] (auto &&cc) { this->guild_member_add_role(guild_id, user_id, role_id, cc); }; +async cluster::co_guild_member_add_role(snowflake guild_id, snowflake user_id, snowflake role_id) { + return async{ this, static_cast(&cluster::guild_member_add_role), guild_id, user_id, role_id }; } -awaitable cluster::co_guild_member_delete(snowflake guild_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_member_delete(guild_id, user_id, cc); }; +async cluster::co_guild_member_delete(snowflake guild_id, snowflake user_id) { + return async{ this, static_cast(&cluster::guild_member_delete), guild_id, user_id }; } -awaitable cluster::co_guild_member_kick(snowflake guild_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_member_kick(guild_id, user_id, cc); }; +async cluster::co_guild_member_kick(snowflake guild_id, snowflake user_id) { + return async{ this, static_cast(&cluster::guild_member_kick), guild_id, user_id }; } -awaitable cluster::co_guild_member_timeout(snowflake guild_id, snowflake user_id, time_t communication_disabled_until) { - return [=, this] (auto &&cc) { this->guild_member_timeout(guild_id, user_id, communication_disabled_until, cc); }; +async cluster::co_guild_member_timeout(snowflake guild_id, snowflake user_id, time_t communication_disabled_until) { + return async{ this, static_cast(&cluster::guild_member_timeout), guild_id, user_id, communication_disabled_until }; } -awaitable cluster::co_guild_member_delete_role(snowflake guild_id, snowflake user_id, snowflake role_id) { - return [=, this] (auto &&cc) { this->guild_member_delete_role(guild_id, user_id, role_id, cc); }; +async cluster::co_guild_member_delete_role(snowflake guild_id, snowflake user_id, snowflake role_id) { + return async{ this, static_cast(&cluster::guild_member_delete_role), guild_id, user_id, role_id }; } -awaitable cluster::co_guild_member_remove_role(snowflake guild_id, snowflake user_id, snowflake role_id) { - return [=, this] (auto &&cc) { this->guild_member_remove_role(guild_id, user_id, role_id, cc); }; +async cluster::co_guild_member_remove_role(snowflake guild_id, snowflake user_id, snowflake role_id) { + return async{ this, static_cast(&cluster::guild_member_remove_role), guild_id, user_id, role_id }; } -awaitable cluster::co_guild_member_move(const snowflake channel_id, const snowflake guild_id, const snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_member_move(channel_id, guild_id, user_id, cc); }; +async cluster::co_guild_member_move(const snowflake channel_id, const snowflake guild_id, const snowflake user_id) { + return async{ this, static_cast(&cluster::guild_member_move), channel_id, guild_id, user_id }; } -awaitable cluster::co_guild_search_members(snowflake guild_id, const std::string& query, uint16_t limit) { - return [=, this] (auto &&cc) { this->guild_search_members(guild_id, query, limit, cc); }; +async cluster::co_guild_search_members(snowflake guild_id, const std::string& query, uint16_t limit) { + return async{ this, static_cast(&cluster::guild_search_members), guild_id, query, limit }; } -awaitable cluster::co_guild_get_invites(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_invites(guild_id, cc); }; +async cluster::co_guild_get_invites(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_get_invites), guild_id }; } -awaitable cluster::co_invite_delete(const std::string &invitecode) { - return [=, this] (auto &&cc) { this->invite_delete(invitecode, cc); }; +async cluster::co_invite_delete(const std::string &invitecode) { + return async{ this, static_cast(&cluster::invite_delete), invitecode }; } -awaitable cluster::co_invite_get(const std::string &invite_code) { - return [=, this] (auto &&cc) { this->invite_get(invite_code, cc); }; +async cluster::co_invite_get(const std::string &invite_code) { + return async{ this, static_cast(&cluster::invite_get), invite_code }; } -awaitable cluster::co_message_add_reaction(const struct message &m, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_add_reaction(m, reaction, cc); }; +async cluster::co_message_add_reaction(const struct message &m, const std::string &reaction) { + return async{ this, static_cast(&cluster::message_add_reaction), m, reaction }; } -awaitable cluster::co_message_add_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_add_reaction(message_id, channel_id, reaction, cc); }; +async cluster::co_message_add_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction) { + return async{ this, static_cast(&cluster::message_add_reaction), message_id, channel_id, reaction }; } -awaitable cluster::co_message_create(const message &m) { - return [=, this] (auto &&cc) { this->message_create(m, cc); }; +async cluster::co_message_create(const message &m) { + return async{ this, static_cast(&cluster::message_create), m }; } -awaitable cluster::co_message_crosspost(snowflake message_id, snowflake channel_id) { - return [=, this] (auto &&cc) { this->message_crosspost(message_id, channel_id, cc); }; +async cluster::co_message_crosspost(snowflake message_id, snowflake channel_id) { + return async{ this, static_cast(&cluster::message_crosspost), message_id, channel_id }; } -awaitable cluster::co_message_delete_all_reactions(const struct message &m) { - return [=, this] (auto &&cc) { this->message_delete_all_reactions(m, cc); }; +async cluster::co_message_delete_all_reactions(const struct message &m) { + return async{ this, static_cast(&cluster::message_delete_all_reactions), m }; } -awaitable cluster::co_message_delete_all_reactions(snowflake message_id, snowflake channel_id) { - return [=, this] (auto &&cc) { this->message_delete_all_reactions(message_id, channel_id, cc); }; +async cluster::co_message_delete_all_reactions(snowflake message_id, snowflake channel_id) { + return async{ this, static_cast(&cluster::message_delete_all_reactions), message_id, channel_id }; } -awaitable cluster::co_message_delete_bulk(const std::vector& message_ids, snowflake channel_id) { - return [=, this] (auto &&cc) { this->message_delete_bulk(message_ids, channel_id, cc); }; +async cluster::co_message_delete_bulk(const std::vector& message_ids, snowflake channel_id) { + return async{ this, static_cast&, snowflake, command_completion_event_t)>(&cluster::message_delete_bulk), message_ids, channel_id }; } -awaitable cluster::co_message_delete(snowflake message_id, snowflake channel_id) { - return [=, this] (auto &&cc) { this->message_delete(message_id, channel_id, cc); }; +async cluster::co_message_delete(snowflake message_id, snowflake channel_id) { + return async{ this, static_cast(&cluster::message_delete), message_id, channel_id }; } -awaitable cluster::co_message_delete_own_reaction(const struct message &m, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_own_reaction(m, reaction, cc); }; +async cluster::co_message_delete_own_reaction(const struct message &m, const std::string &reaction) { + return async{ this, static_cast(&cluster::message_delete_own_reaction), m, reaction }; } -awaitable cluster::co_message_delete_own_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_own_reaction(message_id, channel_id, reaction, cc); }; +async cluster::co_message_delete_own_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction) { + return async{ this, static_cast(&cluster::message_delete_own_reaction), message_id, channel_id, reaction }; } -awaitable cluster::co_message_delete_reaction(const struct message &m, snowflake user_id, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_reaction(m, user_id, reaction, cc); }; +async cluster::co_message_delete_reaction(const struct message &m, snowflake user_id, const std::string &reaction) { + return async{ this, static_cast(&cluster::message_delete_reaction), m, user_id, reaction }; } -awaitable cluster::co_message_delete_reaction(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_reaction(message_id, channel_id, user_id, reaction, cc); }; +async cluster::co_message_delete_reaction(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction) { + return async{ this, static_cast(&cluster::message_delete_reaction), message_id, channel_id, user_id, reaction }; } -awaitable cluster::co_message_delete_reaction_emoji(const struct message &m, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_reaction_emoji(m, reaction, cc); }; +async cluster::co_message_delete_reaction_emoji(const struct message &m, const std::string &reaction) { + return async{ this, static_cast(&cluster::message_delete_reaction_emoji), m, reaction }; } -awaitable cluster::co_message_delete_reaction_emoji(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_reaction_emoji(message_id, channel_id, reaction, cc); }; +async cluster::co_message_delete_reaction_emoji(snowflake message_id, snowflake channel_id, const std::string &reaction) { + return async{ this, static_cast(&cluster::message_delete_reaction_emoji), message_id, channel_id, reaction }; } -awaitable cluster::co_message_edit(const message &m) { - return [=, this] (auto &&cc) { this->message_edit(m, cc); }; +async cluster::co_message_edit(const message &m) { + return async{ this, static_cast(&cluster::message_edit), m }; } -awaitable cluster::co_message_get(snowflake message_id, snowflake channel_id) { - return [=, this] (auto &&cc) { this->message_get(message_id, channel_id, cc); }; +async cluster::co_message_get(snowflake message_id, snowflake channel_id) { + return async{ this, static_cast(&cluster::message_get), message_id, channel_id }; } -awaitable cluster::co_message_get_reactions(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { - return [=, this] (auto &&cc) { this->message_get_reactions(m, reaction, before, after, limit, cc); }; +async cluster::co_message_get_reactions(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { + return async{ this, static_cast(&cluster::message_get_reactions), m, reaction, before, after, limit }; } -awaitable cluster::co_message_get_reactions(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { - return [=, this] (auto &&cc) { this->message_get_reactions(message_id, channel_id, reaction, before, after, limit, cc); }; +async cluster::co_message_get_reactions(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { + return async{ this, static_cast(&cluster::message_get_reactions), message_id, channel_id, reaction, before, after, limit }; } -awaitable cluster::co_message_pin(snowflake channel_id, snowflake message_id) { - return [=, this] (auto &&cc) { this->message_pin(channel_id, message_id, cc); }; +async cluster::co_message_pin(snowflake channel_id, snowflake message_id) { + return async{ this, static_cast(&cluster::message_pin), channel_id, message_id }; } -awaitable cluster::co_messages_get(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit) { - return [=, this] (auto &&cc) { this->messages_get(channel_id, around, before, after, limit, cc); }; +async cluster::co_messages_get(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit) { + return async{ this, static_cast(&cluster::messages_get), channel_id, around, before, after, limit }; } -awaitable cluster::co_message_unpin(snowflake channel_id, snowflake message_id) { - return [=, this] (auto &&cc) { this->message_unpin(channel_id, message_id, cc); }; +async cluster::co_message_unpin(snowflake channel_id, snowflake message_id) { + return async{ this, static_cast(&cluster::message_unpin), channel_id, message_id }; } -awaitable cluster::co_channel_pins_get(snowflake channel_id) { - return [=, this] (auto &&cc) { this->channel_pins_get(channel_id, cc); }; +async cluster::co_channel_pins_get(snowflake channel_id) { + return async{ this, static_cast(&cluster::channel_pins_get), channel_id }; } -awaitable cluster::co_role_create(const class role &r) { - return [=, this] (auto &&cc) { this->role_create(r, cc); }; +async cluster::co_role_create(const class role &r) { + return async{ this, static_cast(&cluster::role_create), r }; } -awaitable cluster::co_role_delete(snowflake guild_id, snowflake role_id) { - return [=, this] (auto &&cc) { this->role_delete(guild_id, role_id, cc); }; +async cluster::co_role_delete(snowflake guild_id, snowflake role_id) { + return async{ this, static_cast(&cluster::role_delete), guild_id, role_id }; } -awaitable cluster::co_role_edit(const class role &r) { - return [=, this] (auto &&cc) { this->role_edit(r, cc); }; +async cluster::co_role_edit(const class role &r) { + return async{ this, static_cast(&cluster::role_edit), r }; } -awaitable cluster::co_roles_edit_position(snowflake guild_id, const std::vector &roles) { - return [=, this] (auto &&cc) { this->roles_edit_position(guild_id, roles, cc); }; +async cluster::co_roles_edit_position(snowflake guild_id, const std::vector &roles) { + return async{ this, static_cast &, command_completion_event_t)>(&cluster::roles_edit_position), guild_id, roles }; } -awaitable cluster::co_roles_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->roles_get(guild_id, cc); }; +async cluster::co_roles_get(snowflake guild_id) { + return async{ this, static_cast(&cluster::roles_get), guild_id }; } -awaitable cluster::co_application_role_connection_get(snowflake application_id) { - return [=, this] (auto &&cc) { this->application_role_connection_get(application_id, cc); }; +async cluster::co_application_role_connection_get(snowflake application_id) { + return async{ this, static_cast(&cluster::application_role_connection_get), application_id }; } -awaitable cluster::co_application_role_connection_update(snowflake application_id, const std::vector &connection_metadata) { - return [=, this] (auto &&cc) { this->application_role_connection_update(application_id, connection_metadata, cc); }; +async cluster::co_application_role_connection_update(snowflake application_id, const std::vector &connection_metadata) { + return async{ this, static_cast &, command_completion_event_t)>(&cluster::application_role_connection_update), application_id, connection_metadata }; } -awaitable cluster::co_user_application_role_connection_get(snowflake application_id) { - return [=, this] (auto &&cc) { this->user_application_role_connection_get(application_id, cc); }; +async cluster::co_user_application_role_connection_get(snowflake application_id) { + return async{ this, static_cast(&cluster::user_application_role_connection_get), application_id }; } -awaitable cluster::co_user_application_role_connection_update(snowflake application_id, const application_role_connection &connection) { - return [=, this] (auto &&cc) { this->user_application_role_connection_update(application_id, connection, cc); }; +async cluster::co_user_application_role_connection_update(snowflake application_id, const application_role_connection &connection) { + return async{ this, static_cast(&cluster::user_application_role_connection_update), application_id, connection }; } -awaitable cluster::co_guild_events_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_events_get(guild_id, cc); }; +async cluster::co_guild_events_get(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_events_get), guild_id }; } -awaitable cluster::co_guild_event_create(const scheduled_event& event) { - return [=, this] (auto &&cc) { this->guild_event_create(event, cc); }; +async cluster::co_guild_event_create(const scheduled_event& event) { + return async{ this, static_cast(&cluster::guild_event_create), event }; } -awaitable cluster::co_guild_event_delete(snowflake event_id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_event_delete(event_id, guild_id, cc); }; +async cluster::co_guild_event_delete(snowflake event_id, snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_event_delete), event_id, guild_id }; } -awaitable cluster::co_guild_event_edit(const scheduled_event& event) { - return [=, this] (auto &&cc) { this->guild_event_edit(event, cc); }; +async cluster::co_guild_event_edit(const scheduled_event& event) { + return async{ this, static_cast(&cluster::guild_event_edit), event }; } -awaitable cluster::co_guild_event_get(snowflake guild_id, snowflake event_id) { - return [=, this] (auto &&cc) { this->guild_event_get(guild_id, event_id, cc); }; +async cluster::co_guild_event_get(snowflake guild_id, snowflake event_id) { + return async{ this, static_cast(&cluster::guild_event_get), guild_id, event_id }; } -awaitable cluster::co_stage_instance_create(const stage_instance& si) { - return [=, this] (auto &&cc) { this->stage_instance_create(si, cc); }; +async cluster::co_stage_instance_create(const stage_instance& si) { + return async{ this, static_cast(&cluster::stage_instance_create), si }; } -awaitable cluster::co_stage_instance_get(const snowflake channel_id) { - return [=, this] (auto &&cc) { this->stage_instance_get(channel_id, cc); }; +async cluster::co_stage_instance_get(const snowflake channel_id) { + return async{ this, static_cast(&cluster::stage_instance_get), channel_id }; } -awaitable cluster::co_stage_instance_edit(const stage_instance& si) { - return [=, this] (auto &&cc) { this->stage_instance_edit(si, cc); }; +async cluster::co_stage_instance_edit(const stage_instance& si) { + return async{ this, static_cast(&cluster::stage_instance_edit), si }; } -awaitable cluster::co_stage_instance_delete(const snowflake channel_id) { - return [=, this] (auto &&cc) { this->stage_instance_delete(channel_id, cc); }; +async cluster::co_stage_instance_delete(const snowflake channel_id) { + return async{ this, static_cast(&cluster::stage_instance_delete), channel_id }; } -awaitable cluster::co_guild_sticker_create(const sticker &s) { - return [=, this] (auto &&cc) { this->guild_sticker_create(s, cc); }; +async cluster::co_guild_sticker_create(const sticker &s) { + return async{ this, static_cast(&cluster::guild_sticker_create), s }; } -awaitable cluster::co_guild_sticker_delete(snowflake sticker_id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_sticker_delete(sticker_id, guild_id, cc); }; +async cluster::co_guild_sticker_delete(snowflake sticker_id, snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_sticker_delete), sticker_id, guild_id }; } -awaitable cluster::co_guild_sticker_get(snowflake id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_sticker_get(id, guild_id, cc); }; +async cluster::co_guild_sticker_get(snowflake id, snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_sticker_get), id, guild_id }; } -awaitable cluster::co_guild_sticker_modify(const sticker &s) { - return [=, this] (auto &&cc) { this->guild_sticker_modify(s, cc); }; +async cluster::co_guild_sticker_modify(const sticker &s) { + return async{ this, static_cast(&cluster::guild_sticker_modify), s }; } -awaitable cluster::co_guild_stickers_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_stickers_get(guild_id, cc); }; +async cluster::co_guild_stickers_get(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_stickers_get), guild_id }; } -awaitable cluster::co_nitro_sticker_get(snowflake id) { - return [=, this] (auto &&cc) { this->nitro_sticker_get(id, cc); }; +async cluster::co_nitro_sticker_get(snowflake id) { + return async{ this, static_cast(&cluster::nitro_sticker_get), id }; } -awaitable cluster::co_sticker_packs_get() { - return [=, this] (auto &&cc) { this->sticker_packs_get(cc); }; +async cluster::co_sticker_packs_get() { + return async{ this, static_cast(&cluster::sticker_packs_get) }; } -awaitable cluster::co_guild_create_from_template(const std::string &code, const std::string &name) { - return [=, this] (auto &&cc) { this->guild_create_from_template(code, name, cc); }; +async cluster::co_guild_create_from_template(const std::string &code, const std::string &name) { + return async{ this, static_cast(&cluster::guild_create_from_template), code, name }; } -awaitable cluster::co_guild_template_create(snowflake guild_id, const std::string &name, const std::string &description) { - return [=, this] (auto &&cc) { this->guild_template_create(guild_id, name, description, cc); }; +async cluster::co_guild_template_create(snowflake guild_id, const std::string &name, const std::string &description) { + return async{ this, static_cast(&cluster::guild_template_create), guild_id, name, description }; } -awaitable cluster::co_guild_template_delete(snowflake guild_id, const std::string &code) { - return [=, this] (auto &&cc) { this->guild_template_delete(guild_id, code, cc); }; +async cluster::co_guild_template_delete(snowflake guild_id, const std::string &code) { + return async{ this, static_cast(&cluster::guild_template_delete), guild_id, code }; } -awaitable cluster::co_guild_template_modify(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description) { - return [=, this] (auto &&cc) { this->guild_template_modify(guild_id, code, name, description, cc); }; +async cluster::co_guild_template_modify(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description) { + return async{ this, static_cast(&cluster::guild_template_modify), guild_id, code, name, description }; } -awaitable cluster::co_guild_templates_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_templates_get(guild_id, cc); }; +async cluster::co_guild_templates_get(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_templates_get), guild_id }; } -awaitable cluster::co_guild_template_sync(snowflake guild_id, const std::string &code) { - return [=, this] (auto &&cc) { this->guild_template_sync(guild_id, code, cc); }; +async cluster::co_guild_template_sync(snowflake guild_id, const std::string &code) { + return async{ this, static_cast(&cluster::guild_template_sync), guild_id, code }; } -awaitable cluster::co_template_get(const std::string &code) { - return [=, this] (auto &&cc) { this->template_get(code, cc); }; +async cluster::co_template_get(const std::string &code) { + return async{ this, static_cast(&cluster::template_get), code }; } -awaitable cluster::co_current_user_join_thread(snowflake thread_id) { - return [=, this] (auto &&cc) { this->current_user_join_thread(thread_id, cc); }; +async cluster::co_current_user_join_thread(snowflake thread_id) { + return async{ this, static_cast(&cluster::current_user_join_thread), thread_id }; } -awaitable cluster::co_current_user_leave_thread(snowflake thread_id) { - return [=, this] (auto &&cc) { this->current_user_leave_thread(thread_id, cc); }; +async cluster::co_current_user_leave_thread(snowflake thread_id) { + return async{ this, static_cast(&cluster::current_user_leave_thread), thread_id }; } -awaitable cluster::co_threads_get_active(snowflake guild_id) { - return [=, this] (auto &&cc) { this->threads_get_active(guild_id, cc); }; +async cluster::co_threads_get_active(snowflake guild_id) { + return async{ this, static_cast(&cluster::threads_get_active), guild_id }; } -awaitable cluster::co_threads_get_joined_private_archived(snowflake channel_id, snowflake before_id, uint16_t limit) { - return [=, this] (auto &&cc) { this->threads_get_joined_private_archived(channel_id, before_id, limit, cc); }; +async cluster::co_threads_get_joined_private_archived(snowflake channel_id, snowflake before_id, uint16_t limit) { + return async{ this, static_cast(&cluster::threads_get_joined_private_archived), channel_id, before_id, limit }; } -awaitable cluster::co_threads_get_private_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit) { - return [=, this] (auto &&cc) { this->threads_get_private_archived(channel_id, before_timestamp, limit, cc); }; +async cluster::co_threads_get_private_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit) { + return async{ this, static_cast(&cluster::threads_get_private_archived), channel_id, before_timestamp, limit }; } -awaitable cluster::co_threads_get_public_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit) { - return [=, this] (auto &&cc) { this->threads_get_public_archived(channel_id, before_timestamp, limit, cc); }; +async cluster::co_threads_get_public_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit) { + return async{ this, static_cast(&cluster::threads_get_public_archived), channel_id, before_timestamp, limit }; } -awaitable cluster::co_thread_member_get(const snowflake thread_id, const snowflake user_id) { - return [=, this] (auto &&cc) { this->thread_member_get(thread_id, user_id, cc); }; +async cluster::co_thread_member_get(const snowflake thread_id, const snowflake user_id) { + return async{ this, static_cast(&cluster::thread_member_get), thread_id, user_id }; } -awaitable cluster::co_thread_members_get(snowflake thread_id) { - return [=, this] (auto &&cc) { this->thread_members_get(thread_id, cc); }; +async cluster::co_thread_members_get(snowflake thread_id) { + return async{ this, static_cast(&cluster::thread_members_get), thread_id }; } -awaitable cluster::co_thread_create_in_forum(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags) { - return [=, this] (auto &&cc) { this->thread_create_in_forum(thread_name, channel_id, msg, auto_archive_duration, rate_limit_per_user, applied_tags, cc); }; +async cluster::co_thread_create_in_forum(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags) { + return async{ this, static_cast, command_completion_event_t)>(&cluster::thread_create_in_forum), thread_name, channel_id, msg, auto_archive_duration, rate_limit_per_user, applied_tags }; } -awaitable cluster::co_thread_create(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user) { - return [=, this] (auto &&cc) { this->thread_create(thread_name, channel_id, auto_archive_duration, thread_type, invitable, rate_limit_per_user, cc); }; +async cluster::co_thread_create(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user) { + return async{ this, static_cast(&cluster::thread_create), thread_name, channel_id, auto_archive_duration, thread_type, invitable, rate_limit_per_user }; } -awaitable cluster::co_thread_edit(const thread &t) { - return [=, this] (auto &&cc) { this->thread_edit(t, cc); }; +async cluster::co_thread_edit(const thread &t) { + return async{ this, static_cast(&cluster::thread_edit), t }; } -awaitable cluster::co_thread_create_with_message(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user) { - return [=, this] (auto &&cc) { this->thread_create_with_message(thread_name, channel_id, message_id, auto_archive_duration, rate_limit_per_user, cc); }; +async cluster::co_thread_create_with_message(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user) { + return async{ this, static_cast(&cluster::thread_create_with_message), thread_name, channel_id, message_id, auto_archive_duration, rate_limit_per_user }; } -awaitable cluster::co_thread_member_add(snowflake thread_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->thread_member_add(thread_id, user_id, cc); }; +async cluster::co_thread_member_add(snowflake thread_id, snowflake user_id) { + return async{ this, static_cast(&cluster::thread_member_add), thread_id, user_id }; } -awaitable cluster::co_thread_member_remove(snowflake thread_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->thread_member_remove(thread_id, user_id, cc); }; +async cluster::co_thread_member_remove(snowflake thread_id, snowflake user_id) { + return async{ this, static_cast(&cluster::thread_member_remove), thread_id, user_id }; } -awaitable cluster::co_current_user_edit(const std::string &nickname, const std::string& image_blob, const image_type type) { - return [=, this] (auto &&cc) { this->current_user_edit(nickname, image_blob, type, cc); }; +async cluster::co_current_user_edit(const std::string &nickname, const std::string& image_blob, const image_type type) { + return async{ this, static_cast(&cluster::current_user_edit), nickname, image_blob, type }; } -awaitable cluster::co_current_application_get() { - return [=, this] (auto &&cc) { this->current_application_get(cc); }; +async cluster::co_current_application_get() { + return async{ this, static_cast(&cluster::current_application_get) }; } -awaitable cluster::co_current_user_get() { - return [=, this] (auto &&cc) { this->current_user_get(cc); }; +async cluster::co_current_user_get() { + return async{ this, static_cast(&cluster::current_user_get) }; } -awaitable cluster::co_current_user_set_voice_state(snowflake guild_id, snowflake channel_id, bool suppress, time_t request_to_speak_timestamp) { - return [=, this] (auto &&cc) { this->current_user_set_voice_state(guild_id, channel_id, suppress, request_to_speak_timestamp, cc); }; +async cluster::co_current_user_set_voice_state(snowflake guild_id, snowflake channel_id, bool suppress, time_t request_to_speak_timestamp) { + return async{ this, static_cast(&cluster::current_user_set_voice_state), guild_id, channel_id, suppress, request_to_speak_timestamp }; } -awaitable cluster::co_user_set_voice_state(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress) { - return [=, this] (auto &&cc) { this->user_set_voice_state(user_id, guild_id, channel_id, suppress, cc); }; +async cluster::co_user_set_voice_state(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress) { + return async{ this, static_cast(&cluster::user_set_voice_state), user_id, guild_id, channel_id, suppress }; } -awaitable cluster::co_current_user_connections_get() { - return [=, this] (auto &&cc) { this->current_user_connections_get(cc); }; +async cluster::co_current_user_connections_get() { + return async{ this, static_cast(&cluster::current_user_connections_get) }; } -awaitable cluster::co_current_user_get_guilds() { - return [=, this] (auto &&cc) { this->current_user_get_guilds(cc); }; +async cluster::co_current_user_get_guilds() { + return async{ this, static_cast(&cluster::current_user_get_guilds) }; } -awaitable cluster::co_current_user_leave_guild(snowflake guild_id) { - return [=, this] (auto &&cc) { this->current_user_leave_guild(guild_id, cc); }; +async cluster::co_current_user_leave_guild(snowflake guild_id) { + return async{ this, static_cast(&cluster::current_user_leave_guild), guild_id }; } -awaitable cluster::co_user_get(snowflake user_id) { - return [=, this] (auto &&cc) { this->user_get(user_id, cc); }; +async cluster::co_user_get(snowflake user_id) { + return async{ this, static_cast(&cluster::user_get), user_id }; } -awaitable cluster::co_user_get_cached(snowflake user_id) { - return [=, this] (auto &&cc) { this->user_get_cached(user_id, cc); }; +async cluster::co_user_get_cached(snowflake user_id) { + return async{ this, static_cast(&cluster::user_get_cached), user_id }; } -awaitable cluster::co_get_voice_regions() { - return [=, this] (auto &&cc) { this->get_voice_regions(cc); }; +async cluster::co_get_voice_regions() { + return async{ this, static_cast(&cluster::get_voice_regions) }; } -awaitable cluster::co_guild_get_voice_regions(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_voice_regions(guild_id, cc); }; +async cluster::co_guild_get_voice_regions(snowflake guild_id) { + return async{ this, static_cast(&cluster::guild_get_voice_regions), guild_id }; } -awaitable cluster::co_create_webhook(const class webhook &w) { - return [=, this] (auto &&cc) { this->create_webhook(w, cc); }; +async cluster::co_create_webhook(const class webhook &w) { + return async{ this, static_cast(&cluster::create_webhook), w }; } -awaitable cluster::co_delete_webhook(snowflake webhook_id) { - return [=, this] (auto &&cc) { this->delete_webhook(webhook_id, cc); }; +async cluster::co_delete_webhook(snowflake webhook_id) { + return async{ this, static_cast(&cluster::delete_webhook), webhook_id }; } -awaitable cluster::co_delete_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id) { - return [=, this] (auto &&cc) { this->delete_webhook_message(wh, message_id, thread_id, cc); }; +async cluster::co_delete_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id) { + return async{ this, static_cast(&cluster::delete_webhook_message), wh, message_id, thread_id }; } -awaitable cluster::co_delete_webhook_with_token(snowflake webhook_id, const std::string &token) { - return [=, this] (auto &&cc) { this->delete_webhook_with_token(webhook_id, token, cc); }; +async cluster::co_delete_webhook_with_token(snowflake webhook_id, const std::string &token) { + return async{ this, static_cast(&cluster::delete_webhook_with_token), webhook_id, token }; } -awaitable cluster::co_edit_webhook(const class webhook& wh) { - return [=, this] (auto &&cc) { this->edit_webhook(wh, cc); }; +async cluster::co_edit_webhook(const class webhook& wh) { + return async{ this, static_cast(&cluster::edit_webhook), wh }; } -awaitable cluster::co_edit_webhook_message(const class webhook &wh, const struct message& m, snowflake thread_id) { - return [=, this] (auto &&cc) { this->edit_webhook_message(wh, m, thread_id, cc); }; +async cluster::co_edit_webhook_message(const class webhook &wh, const struct message& m, snowflake thread_id) { + return async{ this, static_cast(&cluster::edit_webhook_message), wh, m, thread_id }; } -awaitable cluster::co_edit_webhook_with_token(const class webhook& wh) { - return [=, this] (auto &&cc) { this->edit_webhook_with_token(wh, cc); }; +async cluster::co_edit_webhook_with_token(const class webhook& wh) { + return async{ this, static_cast(&cluster::edit_webhook_with_token), wh }; } -awaitable cluster::co_execute_webhook(const class webhook &wh, const struct message& m, bool wait, snowflake thread_id, const std::string& thread_name) { - return [=, this] (auto &&cc) { this->execute_webhook(wh, m, wait, thread_id, thread_name, cc); }; +async cluster::co_execute_webhook(const class webhook &wh, const struct message& m, bool wait, snowflake thread_id, const std::string& thread_name) { + return async{ this, static_cast(&cluster::execute_webhook), wh, m, wait, thread_id, thread_name }; } -awaitable cluster::co_get_channel_webhooks(snowflake channel_id) { - return [=, this] (auto &&cc) { this->get_channel_webhooks(channel_id, cc); }; +async cluster::co_get_channel_webhooks(snowflake channel_id) { + return async{ this, static_cast(&cluster::get_channel_webhooks), channel_id }; } -awaitable cluster::co_get_guild_webhooks(snowflake guild_id) { - return [=, this] (auto &&cc) { this->get_guild_webhooks(guild_id, cc); }; +async cluster::co_get_guild_webhooks(snowflake guild_id) { + return async{ this, static_cast(&cluster::get_guild_webhooks), guild_id }; } -awaitable cluster::co_get_webhook(snowflake webhook_id) { - return [=, this] (auto &&cc) { this->get_webhook(webhook_id, cc); }; +async cluster::co_get_webhook(snowflake webhook_id) { + return async{ this, static_cast(&cluster::get_webhook), webhook_id }; } -awaitable cluster::co_get_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id) { - return [=, this] (auto &&cc) { this->get_webhook_message(wh, message_id, thread_id, cc); }; +async cluster::co_get_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id) { + return async{ this, static_cast(&cluster::get_webhook_message), wh, message_id, thread_id }; } -awaitable cluster::co_get_webhook_with_token(snowflake webhook_id, const std::string &token) { - return [=, this] (auto &&cc) { this->get_webhook_with_token(webhook_id, token, cc); }; +async cluster::co_get_webhook_with_token(snowflake webhook_id, const std::string &token) { + return async{ this, static_cast(&cluster::get_webhook_with_token), webhook_id, token }; } }; /* End of auto-generated definitions */ -dpp::awaitable dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, std::multimap headers) { - return awaitable{[=, this, h = std::move(headers)](auto &&cc) { this->request(url, method, cc, postdata, mimetype, h); }}; +dpp::async dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers) { + return async{ [&, this] (C &&cc) { return this->request(url, method, std::forward(cc), postdata, mimetype, headers); }}; } #endif diff --git a/src/dpp/dispatcher.cpp b/src/dpp/dispatcher.cpp index e081434536..a61f6d9673 100644 --- a/src/dpp/dispatcher.cpp +++ b/src/dpp/dispatcher.cpp @@ -209,52 +209,52 @@ void interaction_create_t::delete_original_response(command_completion_event_t c #ifdef DPP_CORO -awaitable interaction_create_t::co_reply() const { - return dpp::awaitable{[this](auto &&cb) { this->reply(cb); }}; +async interaction_create_t::co_reply() const { + return dpp::async{[this] (T&& cb) { this->reply(std::forward(cb)); }}; } -awaitable interaction_create_t::co_reply(interaction_response_type t, const message & m) const { - return dpp::awaitable{[&, this](auto &&cb) { this->reply(t, m, cb); }}; +async interaction_create_t::co_reply(interaction_response_type t, const message & m) const { + return dpp::async{[&, this] (T&& cb) { this->reply(t, m, std::forward(cb)); }}; } -awaitable interaction_create_t::co_reply(interaction_response_type t, const std::string & mt) const { - return dpp::awaitable{[&, this](auto &&cb) { this->reply(t, mt, cb); }}; +async interaction_create_t::co_reply(interaction_response_type t, const std::string & mt) const { + return dpp::async{[&, this] (T&& cb) { this->reply(t, mt, std::forward(cb)); }}; } -awaitable interaction_create_t::co_reply(const message & m) const { - return dpp::awaitable{[&, this](auto &&cb) { this->reply(m, cb); }}; +async interaction_create_t::co_reply(const message & m) const { + return dpp::async{[&, this] (T&& cb) { this->reply(m, std::forward(cb)); }}; } -awaitable interaction_create_t::co_reply(const std::string & mt) const { - return dpp::awaitable{[&, this](auto &&cb) { this->reply(mt, cb); }}; +async interaction_create_t::co_reply(const std::string & mt) const { + return dpp::async{[&, this] (T&& cb) { this->reply(mt, std::forward(cb)); }}; } -awaitable interaction_create_t::co_dialog(const interaction_modal_response& mr) const { - return dpp::awaitable{[&, this](auto &&cb) { this->dialog(mr, cb); }}; +async interaction_create_t::co_dialog(const interaction_modal_response& mr) const { + return dpp::async{[&, this] (T&& cb) { this->dialog(mr, std::forward(cb)); }}; } -awaitable interaction_create_t::co_edit_response(const message & m) const { - return dpp::awaitable{[&, this](auto &&cb) { this->edit_response(m, cb); }}; +async interaction_create_t::co_edit_response(const message & m) const { + return dpp::async{[&, this] (T&& cb) { this->edit_response(m, std::forward(cb)); }}; } -awaitable interaction_create_t::co_edit_response(const std::string & mt) const { - return dpp::awaitable{[&, this](auto &&cb) { this->edit_response(mt, cb); }}; +async interaction_create_t::co_edit_response(const std::string & mt) const { + return dpp::async{[&, this] (T&& cb) { this->edit_response(mt, std::forward(cb)); }}; } -awaitable interaction_create_t::co_thinking(bool ephemeral) const { - return dpp::awaitable{[&, this](auto &&cb) { this->thinking(ephemeral, cb); }}; +async interaction_create_t::co_thinking(bool ephemeral) const { + return dpp::async{[&, this] (T&& cb) { this->thinking(ephemeral, std::forward(cb)); }}; } -awaitable interaction_create_t::co_get_original_response() const { - return dpp::awaitable{[&, this](auto &&cb) { this->get_original_response(cb); }}; +async interaction_create_t::co_get_original_response() const { + return dpp::async{[&, this] (T&& cb) { this->get_original_response(std::forward(cb)); }}; } -awaitable interaction_create_t::co_edit_original_response(const message & m) const { - return dpp::awaitable{[&, this](auto &&cb) { this->edit_original_response(m, cb); }}; +async interaction_create_t::co_edit_original_response(const message & m) const { + return dpp::async{[&, this] (T&& cb) { this->edit_original_response(m, std::forward(cb)); }}; } -awaitable interaction_create_t::co_delete_original_response() const { - return dpp::awaitable{[&, this](auto &&cb) { this->delete_original_response(cb); }}; +async interaction_create_t::co_delete_original_response() const { + return dpp::async{[&, this] (T&& cb) { this->delete_original_response(std::forward(cb)); }}; } #endif /* DPP_CORO */ From d18fb48ed1533eb1c47e93dbc817649a874466d2 Mon Sep 17 00:00:00 2001 From: Amber Ehrlich Date: Sat, 12 Aug 2023 12:02:05 -0400 Subject: [PATCH 13/21] feat(coro): dpp::task::cancel(), lock-free dpp::task --- include/dpp/coro/coro.h | 92 +++++++- include/dpp/coro/job.h | 26 ++- include/dpp/coro/task.h | 468 +++++++++++++++++++++++++++++----------- include/dpp/exception.h | 12 +- 4 files changed, 472 insertions(+), 126 deletions(-) diff --git a/include/dpp/coro/coro.h b/include/dpp/coro/coro.h index 656bebcfae..5122ae37e5 100644 --- a/include/dpp/coro/coro.h +++ b/include/dpp/coro/coro.h @@ -65,6 +65,86 @@ namespace std_coroutine = std; # endif #endif +#ifndef _DOXYGEN_ +/** + * @brief Concept to check if a type has a useable `operator co_await()` member + */ +template +concept has_co_await_member = requires (T expr) { expr.operator co_await(); }; + +/** + * @brief Concept to check if a type has a useable overload of the free function `operator co_await(expr)` + */ +template +concept has_free_co_await = requires (T expr) { operator co_await(expr); }; + +/** + * @brief Concept to check if a type has useable `await_ready()`, `await_suspend()` and `await_resume()` member functions. + */ +template +concept has_await_members = requires (T expr) { expr.await_ready(); expr.await_suspend(); expr.await_resume(); }; + +/** + * @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr` + */ +template +requires (has_co_await_member) +decltype(auto) co_await_resolve(T&& expr) noexcept(noexcept(expr.operator co_await())) { + decltype(auto) awaiter = expr.operator co_await(); + return awaiter; +} + +/** + * @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr` + */ +template +requires (!has_co_await_member && has_free_co_await) +decltype(auto) co_await_resolve(T&& expr) noexcept(noexcept(operator co_await(expr))) { + decltype(auto) awaiter = operator co_await(static_cast(expr)); + return awaiter; +} + +/** + * @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr` + */ +template +requires (!has_co_await_member && !has_free_co_await) +decltype(auto) co_await_resolve(T&& expr) noexcept { + return static_cast(expr); +} +#else +/** + * @brief Concept to check if a type has a useable `operator co_await()` member + * + * @note This is actually a C++20 concept but Doxygen doesn't do well with them + */ +template +bool has_co_await_member; + +/** + * @brief Concept to check if a type has a useable overload of the free function `operator co_await(expr)` + * + * @note This is actually a C++20 concept but Doxygen doesn't do well with them + */ +template +bool has_free_co_await; + +/** + * @brief Concept to check if a type has useable `await_ready()`, `await_suspend()` and `await_resume()` member functions. + * + * @note This is actually a C++20 concept but Doxygen doesn't do well with them + */ +template +bool has_await_members; + +/** + * @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr` + * + * This function is conditionally noexcept, if the returned expression also is. + */ +decltype(auto) co_await_resolve(auto&& expr) {} +#endif + } // namespace detail struct confirmation_callback_t; @@ -74,7 +154,7 @@ class async; template #ifndef _DOXYGEN_ -requires std::is_same_v || (!std::is_reference_v && std::is_move_constructible_v && std::is_move_assignable_v) +requires (!std::is_reference_v) #endif class task; @@ -83,6 +163,16 @@ class coroutine; struct job; +#ifdef DPP_CORO_TEST +/** + * @brief Allocation count of a certain type, for testing purposes. + * + * @todo Remove when coro is stable + */ +template +inline int coro_alloc_count = 0; +#endif + } // namespace dpp #endif /* DPP_CORO */ diff --git a/include/dpp/coro/job.h b/include/dpp/coro/job.h index ff3e933ad4..9868b04be4 100644 --- a/include/dpp/coro/job.h +++ b/include/dpp/coro/job.h @@ -34,7 +34,8 @@ namespace dpp { * * This object stores no state and is the recommended way to use coroutines if you do not need to co_await the result. * - * @warning It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing. + * @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. + * @warning - It cannot be co_awaited, which means the second it co_awaits something, the program jumps back to the calling function, which continues executing. * At this point, if the function returns, every object declared in the function including its parameters are destroyed, which causes dangling references. * This is exactly the same problem as references in lambdas : https://dpp.dev/lambdas-and-locals.html. * For this reason, `co_await` will error if any parameters are passed by reference. @@ -45,11 +46,26 @@ struct job {}; namespace detail { +#ifdef DPP_CORO_TEST + struct job_promise_base{}; +#endif + /** * @brief Coroutine promise type for a job */ template struct job_promise { + +#ifdef DPP_CORO_TEST + job_promise() { + ++coro_alloc_count; + } + + ~job_promise() { + --coro_alloc_count; + } +#endif + /* * @brief Function called when the job is done. * @@ -112,6 +128,14 @@ struct job_promise { } // namespace dpp +template <> +struct dpp::detail::std_coroutine::coroutine_traits { + /** + * @brief Promise type for this coroutine signature. + */ + using promise_type = dpp::detail::job_promise; +}; + /** * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. */ diff --git a/include/dpp/coro/task.h b/include/dpp/coro/task.h index 703ff031e0..14228ce8f4 100644 --- a/include/dpp/coro/task.h +++ b/include/dpp/coro/task.h @@ -30,11 +30,25 @@ #include #include #include +#include + +#include // std::cerr in final_suspend namespace dpp { namespace detail { +enum class task_state_t { + /** @brief Task was started but never co_await-ed */ + started, + /** @brief Task was co_await-ed and is pending completion */ + awaited, + /** @brief Task is completed */ + done, + /** @brief Task is still running but the actual dpp::task object is destroyed */ + dangling +}; + /** * @brief A task's promise type, with special logic for handling nested tasks. */ @@ -53,21 +67,14 @@ struct task_chain_final_awaiter; template using task_handle = detail::std_coroutine::coroutine_handle>; -} // namespace detail - /** - * @brief A coroutine task. It can be co_awaited to make nested coroutines. + * @brief Base class of dpp::task. This class should not be used directly by a user, use dpp::task instead. * - * Can be used in conjunction with coroutine events via dpp::event_router_t::co_attach, or on its own. - * - * @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. - * @tparam R Return type of the coroutine. Can be void, or a complete object that supports move construction and move assignment. + * @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::task so a user cannot call await_suspend and await_resume directly. */ template -#ifndef _DOXYGEN_ -requires std::is_same_v || (!std::is_reference_v && std::is_move_constructible_v && std::is_move_assignable_v) -#endif -class task { +class task_base { +protected: /** * @brief The coroutine handle of this task. */ @@ -78,84 +85,74 @@ class task { */ friend struct detail::task_promise; +private: /** * @brief Construct from a coroutine handle. Internal use only */ - explicit task(detail::task_handle handle_) : handle(handle_) {} + explicit task_base(detail::task_handle handle_) : handle(handle_) {} public: /** * @brief Default constructor, creates a task not bound to a coroutine. */ - task() = default; + task_base() = default; /** * @brief Copy constructor is disabled */ - task(const task &) = delete; + task_base(const task_base &) = delete; /** * @brief Move constructor, grabs another task's coroutine handle * * @param other Task to move the handle from */ - task(task &&other) noexcept : handle(std::exchange(other.handle, nullptr)) {} - + task_base(task_base &&other) noexcept : handle(std::exchange(other.handle, nullptr)) {} /** * @brief Destructor. * - * Destroys the handle if coroutine is done, otherwise detaches it from this thread. - * In detached mode, the handle will destroy itself at the end of the coroutine. + * Destroys the handle. + * @warning The coroutine must be finished before this is called, otherwise it runs the risk of being resumed after it is destroyed, resuming in use-after-free undefined behavior. */ - ~task() { + ~task_base() { if (handle) { - auto &promise = handle.promise(); - - if (!promise.is_sync) { - std::unique_lock lock{promise.mutex}; - - if (promise.destroy) // promise in async thread checked first and skipped clean up, we do it - { - if (promise.exception && promise.exception_handler) - promise.exception_handler(promise.exception); - lock.unlock(); - handle.destroy(); - } - else - handle.promise().destroy = true; - } - else { - if (promise.exception && promise.exception_handler) - promise.exception_handler(promise.exception); + detail::task_promise &promise = handle.promise(); + detail::task_state_t previous_state = promise.state.exchange(detail::task_state_t::dangling); + + if (previous_state == detail::task_state_t::done) handle.destroy(); - } + else + cancel(); } } /** * @brief Copy assignment is disabled */ - task &operator=(const task &) = delete; + task_base &operator=(const task_base &) = delete; /** * @brief Move assignment, grabs another task's coroutine handle * * @param other Task to move the handle from */ - task &operator=(task &&other) noexcept { + task_base &operator=(task_base &&other) noexcept { handle = std::exchange(other.handle, nullptr); return (*this); } /** - * @brief First function called by the standard library when the task is co_await-ed. + * @brief Check whether or not a call to co_await will suspend the caller. * - * @remark Do not call this manually, use the co_await keyword instead. + * This function is called by the standard library as a first step when using co_await. If it returns true then the caller is not suspended. + * @throws logic_exception if the task is empty. * @return bool Whether not to suspend the caller or not */ - bool await_ready() { - return handle.promise().is_sync; + bool await_ready() const { + if (!handle) + throw dpp::logic_exception{"cannot co_await an empty task"}; + return handle.promise().state.load() == detail::task_state_t::done; } /** @@ -167,52 +164,226 @@ class task { * @param caller The calling coroutine, now suspended * @return bool Whether to suspend the caller or not */ - template - bool await_suspend(detail::std_coroutine::coroutine_handle caller) { - auto &my_promise = handle.promise(); + bool await_suspend(detail::std_coroutine::coroutine_handle<> caller) { + detail::task_promise &my_promise = handle.promise(); + auto previous_state = detail::task_state_t::started; - if (my_promise.is_sync) + my_promise.parent = caller; + // Replace `sent` state with `awaited` ; if that fails, the only logical option is the state was `done`, in which case return false to resume + if (!handle.promise().state.compare_exchange_strong(previous_state, detail::task_state_t::awaited) && previous_state == detail::task_state_t::done) return false; + return true; + } - std::lock_guard lock{my_promise.mutex}; + /** + * @brief Function to check if the task has finished its execution entirely + * + * @return bool Whether the task is finished. + */ + [[nodiscard]] bool done() const noexcept { + return handle && handle.promise().state.load(std::memory_order_relaxed) == detail::task_state_t::done; + } - if (handle.done()) - return (false); - if constexpr (requires (T t) { t.is_sync = false; }) - caller.promise().is_sync = false; - my_promise.parent = caller; - return true; + /** + * @brief Cancel the task, it will stop the next time it uses co_await. On co_await-ing this task, throws dpp::task_cancelled_exception. + */ + dpp::task& cancel() & noexcept { + handle.promise().cancelled.exchange(true, std::memory_order_relaxed); + return static_cast &>(*this); } + /** + * @brief Cancel the task, it will stop the next time it uses co_await. On co_await-ing this task, throws dpp::task_cancelled_exception. + */ + dpp::task&& cancel() && noexcept { + handle.promise().cancelled.exchange(true, std::memory_order_relaxed); + return static_cast &&>(*this); + } + + /** + * @brief Function called by the standard library when resuming. + * + * @return R& Return value of the coroutine, handed to the caller of co_await. + */ + decltype(auto) await_resume() & { + return static_cast &>(*this).await_resume_impl(); + } + + /** + * @brief Function called by the standard library when resuming. + * + * @return const R& Return value of the coroutine, handed to the caller of co_await. + */ + decltype(auto) await_resume() const & { + return static_cast &>(*this).await_resume_impl(); + } + + /** + * @brief Function called by the standard library when resuming. + * + * @return R&& Return value of the coroutine, handed to the caller of co_await. + */ + decltype(auto) await_resume() && { + return static_cast &&>(*this).await_resume_impl(); + } +}; + +} // namespace detail + +/** + * @brief A coroutine task. It starts immediately on construction and can be co_await-ed, making it perfect for parallel coroutines returning a value. + * + * Can be used in conjunction with coroutine events via dpp::event_router_t::co_attach, or on its own. + * + * @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. + * @tparam R Return type of the coroutine. Cannot be a reference, can be void. + */ +template +#ifndef _DOXYGEN_ +requires (!std::is_reference_v) +#endif +class task : private detail::task_base { + /** + * @brief Private base class containing common logic between task and task. It also serves to prevent await_suspend and await_resume from being used directly. + * + * @see operator co_await() + */ + friend class detail::task_base; + + /** + * @brief Function called by the standard library when the coroutine is resumed. + * + * @throw Throws any exception thrown or uncaught by the coroutine + * @return R& The result of the coroutine. This is returned to the awaiter as the result of co_await + */ + R& await_resume_impl() & { + detail::task_promise &promise = this->handle.promise(); + if (promise.exception) + std::rethrow_exception(promise.exception); + return *reinterpret_cast(promise.result_storage.data()); + } + + /** + * @brief Function called by the standard library when the coroutine is resumed. + * + * @throw Throws any exception thrown or uncaught by the coroutine + * @return const R& The result of the coroutine. This is returned to the awaiter as the result of co_await + */ + const R& await_resume_impl() const & { + detail::task_promise &promise = this->handle.promise(); + if (promise.exception) + std::rethrow_exception(promise.exception); + return *reinterpret_cast(promise.result_storage.data()); + } + + /** + * @brief Function called by the standard library when the coroutine is resumed. + * + * @throw Throws any exception thrown or uncaught by the coroutine + * @return R&& The result of the coroutine. This is returned to the awaiter as the result of co_await + */ + R&& await_resume_impl() && { + detail::task_promise &promise = this->handle.promise(); + if (promise.exception) + std::rethrow_exception(promise.exception); + return *reinterpret_cast(promise.result_storage.data()); + } + +public: + using detail::task_base::task_base; // use task_base's constructors + using detail::task_base::operator=; // use task_base's assignment operators + using detail::task_base::done; // expose done() as public + using detail::task_base::cancel; // expose cancel() as public + using detail::task_base::await_ready; // expose await_ready as public + + /** + * @brief Suspend the current coroutine until the task completes. + * + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. + * @return R& On resumption, this expression evaluates to the result object of type R, as a reference. + */ + auto& operator co_await() & noexcept { + return static_cast&>(*this); + } + + /** + * @brief Suspend the current coroutine until the task completes. + * + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. + * @return const R& On resumption, this expression evaluates to the result object of type R, as a const reference. + */ + const auto& operator co_await() const & noexcept { + return static_cast&>(*this); + } + + /** + * @brief Suspend the current coroutine until the task completes. + * + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. + * @return R&& On resumption, this expression evaluates to the result object of type R, as an rvalue reference. + */ + auto&& operator co_await() && noexcept { + return static_cast&&>(*this); + } +}; + +/** + * @brief A coroutine task. It starts immediately on construction and can be co_await-ed, making it perfect for parallel coroutines returning a value. + * + * Can be used in conjunction with coroutine events via dpp::event_router_t::co_attach, or on its own. + * + * @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to GitHub issues or to the D++ Discord server. + * @tparam R Return type of the coroutine. Cannot be a reference, can be void. + */ +template <> +class task : private detail::task_base { + /** + * @brief Private base class containing common logic between task and task. It also serves to prevent await_suspend and await_resume from being used directly. + * + * @see operator co_await() + */ + friend class detail::task_base; + /** * @brief Function called by the standard library when the coroutine is resumed. * * @remark Do not call this manually, use the co_await keyword instead. * @throw Throws any exception thrown or uncaught by the coroutine - * @return R The result of the coroutine. It is the value the whole co-await expression evaluates to */ - R await_resume(); + void await_resume_impl() const; + +public: + using detail::task_base::task_base; // use task_base's constructors + using detail::task_base::operator=; // use task_base's assignment operators + using detail::task_base::done; // expose done() as public + using detail::task_base::cancel; // expose cancel() as public + using detail::task_base::await_ready; // expose await_ready as public + + /** + * @brief Suspend the current coroutine until the task completes. + * + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. + */ + auto& operator co_await() & { + return static_cast&>(*this); + } /** - * @brief Function to check if the coroutine has finished its execution entirely + * @brief Suspend the current coroutine until the task completes. * - * @return bool Whether the coroutine is done. - * @see https://en.cppreference.com/w/cpp/coroutine/coroutine_handle/done + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. */ - bool done() const noexcept { - return handle.done(); + const auto& operator co_await() const & { + return static_cast&>(*this); } /** - * @brief Set the exception handling function. Called when an exception is thrown but not caught + * @brief Suspend the current coroutine until the task completes. * - * @warning The exception handler must not throw. If an exception that is not caught is thrown in a detached task, the program will terminate. + * @throw On resumption, any exception thrown by the coroutine is propagated to the caller. */ - task &on_exception(std::function func) { - handle.promise().exception_handler = std::move(func); - if (handle.promise().exception) - func(handle.promise().exception); - return *this; + auto&& operator co_await() && { + return static_cast&&>(*this); } }; @@ -225,7 +396,7 @@ struct task_chain_final_awaiter { /** * @brief Always suspend at the end of the task. This allows us to clean up and resume the parent */ - bool await_ready() noexcept { + bool await_ready() const noexcept { return (false); } @@ -233,22 +404,28 @@ struct task_chain_final_awaiter { * @brief The suspension logic of the coroutine when it finishes. Always suspend the caller, meaning cleaning up the handle is on us * * @param handle The handle of this coroutine + * @return std::coroutine_handle<> Handle to resume, which is either the parent if present or std::noop_coroutine() otherwise */ - void await_suspend(detail::task_handle handle) noexcept; + std_coroutine::coroutine_handle<> await_suspend(detail::task_handle handle) const noexcept; /* * @brief Function called when this object is co_awaited by the standard library at the end of final_suspend. Do nothing, return nothing */ - void await_resume() noexcept {} + void await_resume() const noexcept {} }; /** * @brief Base implementation of task_promise, without the logic that would depend on the return type. Meant to be inherited from */ struct task_promise_base { /** - * @brief Mutex for async task destruction. + * @brief State of the task, used to keep track of lifetime and status */ - std::mutex mutex{}; + std::atomic state = task_state_t::started; + + /** + * @brief Whether the task is cancelled or not. + */ + std::atomic cancelled = false; /** * @brief Parent coroutine to return to for nested coroutines. @@ -262,39 +439,61 @@ struct task_promise_base { */ std::exception_ptr exception = nullptr; - /** - * @brief Whether the coroutine has async calls or not - * - * Will only ever change on the calling thread while callback mutex guards the async thread - */ - bool is_sync = true; - - /** - * @brief Whether either the task object or the promise is gone and the next one to end will clean up - */ - bool destroy = false; +#ifdef DPP_CORO_TEST + task_promise_base() { + ++coro_alloc_count; + } - /** - * @brief Function object called when an exception is thrown from a coroutine - */ - std::function exception_handler = nullptr; + ~task_promise_base() { + --coro_alloc_count; + } +#endif /** * @brief Function called by the standard library when the coroutine is created. * * @return std::suspend_never Don't suspend, the coroutine starts immediately. */ - std_coroutine::suspend_never initial_suspend() noexcept { + std_coroutine::suspend_never initial_suspend() const noexcept { return {}; } /** * @brief Function called by the standard library when an exception is thrown and not caught in the coroutine. * - * Stores the exception pointer to rethrow later + * Stores the exception pointer to rethrow on co_await. If the task object is destroyed and was not cancelled, throw instead */ void unhandled_exception() { exception = std::current_exception(); + if (state.load() == task_state_t::dangling && !cancelled) + throw; + } + + template + struct proxy_awaiter { + const task_promise_base &promise; + A awaitable; + + bool await_ready() noexcept(noexcept(awaitable.await_ready())) { + return awaitable.await_ready(); + } + + template + decltype(auto) await_suspend(T&& handle) noexcept(noexcept(awaitable.await_suspend(std::forward(handle)))) { + return awaitable.await_suspend(std::forward(handle)); + } + + decltype(auto) await_resume() { + if (promise.cancelled.load()) + throw dpp::task_cancelled_exception{"task was cancelled"}; + return awaitable.await_resume(); + } + }; + + template + auto await_transform(T&& expr) const noexcept(noexcept(co_await_resolve(std::forward(expr)))) { + using awaitable_t = decltype(co_await_resolve(std::forward(expr))); + return proxy_awaiter{*this, co_await_resolve(std::forward(expr))}; } }; @@ -303,12 +502,17 @@ struct task_promise_base { */ template struct task_promise : task_promise_base { + ~task_promise() { + if (state.load() == task_state_t::done && !exception) + std::destroy_at(reinterpret_cast(result_storage.data())); + } + /** * @brief Stored return value of the coroutine. * * @details The main reason we use std::optional here and not R is to avoid default construction of the value so we only require R to have a move constructor, instead of both a default constructor and move assignment operator */ - std::optional value = std::nullopt; + alignas(R) std::array result_storage; /** * @brief Function called by the standard library when the coroutine co_returns a value. @@ -317,8 +521,32 @@ struct task_promise : task_promise_base { * * @param expr The value given to co_return */ - void return_value(R expr) { - value = std::move(expr); + void return_value(R&& expr) noexcept(std::is_nothrow_move_constructible_v) requires std::move_constructible { + std::construct_at(reinterpret_cast(result_storage.data()), static_cast(expr)); + } + + /** + * @brief Function called by the standard library when the coroutine co_returns a value. + * + * Stores the value internally to hand to the caller when it resumes. + * + * @param expr The value given to co_return + */ + void return_value(const R &expr) noexcept(std::is_nothrow_copy_constructible_v) requires std::copy_constructible { + std::construct_at(reinterpret_cast(result_storage.data()), expr); + } + + /** + * @brief Function called by the standard library when the coroutine co_returns a value. + * + * Stores the value internally to hand to the caller when it resumes. + * + * @param expr The value given to co_return + */ + template + requires (!std::is_same_v> && std::convertible_to) + void return_value(T&& expr) noexcept (std::is_nothrow_convertible_v) { + std::construct_at(reinterpret_cast(result_storage.data()), std::forward(expr)); } /** @@ -326,8 +554,8 @@ struct task_promise : task_promise_base { * * @return task The coroutine object */ - task get_return_object() { - return task{task_handle::from_promise(*this)}; + task get_return_object() noexcept { + return task{task_handle::from_promise(*this)}; } /** @@ -335,7 +563,7 @@ struct task_promise : task_promise_base { * * @return task_chain_final_awaiter Special object containing the chain resolution and clean-up logic. */ - task_chain_final_awaiter final_suspend() noexcept { + task_chain_final_awaiter final_suspend() const noexcept { return {}; } }; @@ -350,14 +578,14 @@ struct task_promise : task_promise_base { * * Does nothing but is required by the standard library. */ - void return_void() {} + void return_void() const noexcept {} /** * @brief Function called by the standard library when the coroutine is created. * * @return task The coroutine object */ - task get_return_object() { + task get_return_object() noexcept { return task{task_handle::from_promise(*this)}; } @@ -366,43 +594,37 @@ struct task_promise : task_promise_base { * * @return task_chain_final_awaiter Special object containing the chain resolution and clean-up logic. */ - task_chain_final_awaiter final_suspend() noexcept { + task_chain_final_awaiter final_suspend() const noexcept { return {}; } }; template -void detail::task_chain_final_awaiter::await_suspend(detail::task_handle handle) noexcept { +std_coroutine::coroutine_handle<> detail::task_chain_final_awaiter::await_suspend(task_handle handle) const noexcept { task_promise &promise = handle.promise(); - std_coroutine::coroutine_handle<> parent = promise.parent; - - if (!promise.is_sync) { - std::unique_lock lock{promise.mutex}; - - if (promise.destroy) { - if (promise.exception && promise.exception_handler) - promise.exception_handler(promise.exception); - lock.unlock(); + task_state_t previous_state = promise.state.exchange(task_state_t::done); + + switch (previous_state) { + case task_state_t::started: // started but never awaited, suspend + return std_coroutine::noop_coroutine(); + case task_state_t::awaited: // co_await-ed, resume parent + return promise.parent; + case task_state_t::dangling: // task object is gone, free the handle handle.destroy(); - } - else - promise.destroy = true; // Send the destruction back to the task + return std_coroutine::noop_coroutine(); + case task_state_t::done: // what + // this should never happen. log it. we don't have a cluster so just write it on cerr + std::cerr << "dpp::task: final_suspend called twice. something went very wrong here, please report to GitHub issues or the D++ Discord server" << std::endl; } - if (parent) - parent.resume(); + // TODO: replace with __builtin_unreachable when we confirm this never happens with normal usage + return std_coroutine::noop_coroutine(); } } // namespace detail -template -#ifndef _DOXYGEN_ -requires std::is_same_v || (!std::is_reference_v && std::is_move_constructible_v && std::is_move_assignable_v) -#endif -R task::await_resume() { - if (handle.promise().exception) // If we have an exception, rethrow +inline void task::await_resume_impl() const { + if (handle.promise().exception) std::rethrow_exception(handle.promise().exception); - if constexpr (!std::is_same_v) // If we have a return type, return it and clean up our stored value - return *std::exchange(handle.promise().value, std::nullopt); } } // namespace dpp diff --git a/include/dpp/exception.h b/include/dpp/exception.h index e437f9ee1e..b389ecc1c7 100644 --- a/include/dpp/exception.h +++ b/include/dpp/exception.h @@ -182,10 +182,17 @@ class exception : public std::exception */ class cache_exception : public dpp::exception { }; /** - * @brief Represents an attempt to construct a cluster with an invalid bot token. + * @brief Represents an attempt to construct a cluster with an invalid bot token. * @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception. */ class invalid_token_exception : public dpp::rest_exception { }; +#ifdef DPP_CORO + /** + * @brief Represents the cancellation of a task. Will be thrown to the awaiter of a cancelled task. + * @note This is a stub for documentation purposes. For full information on supported methods please see dpp::exception. + */ + class task_cancelled_exception : public dpp::exception { }; +#endif /* DPP_CORO */ #else derived_exception(logic_exception, exception); derived_exception(file_exception, exception); @@ -196,6 +203,9 @@ class exception : public std::exception derived_exception(length_exception, exception); derived_exception(parse_exception, exception); derived_exception(cache_exception, exception); +# ifdef DPP_CORO + derived_exception(task_cancelled_exception, exception); +# endif /* DPP_CORO */ #endif } // namespace dpp From 54eaffab4aaecba9018d58d42f02cfa79f5949cf Mon Sep 17 00:00:00 2001 From: Amber Ehrlich Date: Tue, 15 Aug 2023 15:22:11 -0400 Subject: [PATCH 14/21] test: add enum for test status and type, make each test a variable, add coro tests --- include/dpp/coro/job.h | 24 +- library/CMakeLists.txt | 49 +-- src/unittest/coro.cpp | 490 +++++++++++++++++++++++++++++ src/unittest/test.cpp | 635 +++++++++++++++++++------------------- src/unittest/test.h | 256 +++++++++++++-- src/unittest/unittest.cpp | 264 +++++----------- 6 files changed, 1156 insertions(+), 562 deletions(-) create mode 100644 src/unittest/coro.cpp diff --git a/include/dpp/coro/job.h b/include/dpp/coro/job.h index 9868b04be4..72308b0c0a 100644 --- a/include/dpp/coro/job.h +++ b/include/dpp/coro/job.h @@ -46,6 +46,12 @@ struct job {}; namespace detail { +template +inline constexpr bool coroutine_has_ref_params_v = (std::is_reference_v || ... || false); + +template +inline constexpr bool coroutine_has_ref_params_v = (std::is_reference_v || ... || (std::is_reference_v && !std::is_invocable_v)); + #ifdef DPP_CORO_TEST struct job_promise_base{}; #endif @@ -53,7 +59,7 @@ namespace detail { /** * @brief Coroutine promise type for a job */ -template +template struct job_promise { #ifdef DPP_CORO_TEST @@ -118,7 +124,7 @@ struct job_promise { * If you must pass a reference, pass it as a pointer or with std::ref, but you must fully understand the reason behind this warning, and what to avoid. * If you prefer a safer type, use `coroutine` for synchronous execution, or `task` for parallel tasks, and co_await them. */ - static_assert(!has_reference_params, "co_await is disabled in dpp::job when taking parameters by reference. read comment above this line for more info"); + static_assert(!coroutine_has_ref_params_v, "co_await is disabled in dpp::job when taking parameters by reference. read comment above this line for more info"); return std::forward(expr); } @@ -128,26 +134,18 @@ struct job_promise { } // namespace dpp -template <> -struct dpp::detail::std_coroutine::coroutine_traits { - /** - * @brief Promise type for this coroutine signature. - */ - using promise_type = dpp::detail::job_promise; -}; - /** * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. */ -template -struct dpp::detail::std_coroutine::coroutine_traits { +template +struct dpp::detail::std_coroutine::coroutine_traits { /** * @brief Promise type for this coroutine signature. * * When the coroutine is created from a lambda, that lambda is passed as a first parameter. * Not ideal but we'll allow any callable that takes the rest of the arguments passed */ - using promise_type = dpp::detail::job_promise<(std::is_reference_v || ... || (std::is_reference_v && !std::is_invocable_v))>; + using promise_type = dpp::detail::job_promise; }; #endif /* DPP_CORO */ diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index bb078a462e..4a8624cab9 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -271,27 +271,6 @@ target_compile_features(dpp PRIVATE cxx_thread_local) target_compile_features(dpp PRIVATE cxx_variadic_templates) target_compile_features(dpp PRIVATE cxx_attribute_deprecated) -if (DPP_BUILD_TEST) - enable_testing(${CMAKE_CURRENT_SOURCE_DIR}/..) - file(GLOB testnamelist "${CMAKE_CURRENT_SOURCE_DIR}/../src/*") - foreach (fulltestname ${testnamelist}) - get_filename_component(testname ${fulltestname} NAME) - if (NOT "${testname}" STREQUAL "dpp") - message("-- Configuring test: ${Green}${testname}${ColourReset} with source: ${modules_dir}/${testname}/*.cpp") - set (testsrc "") - file(GLOB testsrc "${modules_dir}/${testname}/*.cpp") - add_executable(${testname} ${testsrc}) - target_compile_features(${testname} PRIVATE cxx_std_17) - target_link_libraries(${testname} PUBLIC ${modname}) - endif() - endforeach() - add_test( - NAME unittests - COMMAND library/unittest - WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/library - ) -endif() - if(HAVE_PRCTL) target_compile_definitions(dpp PRIVATE HAVE_PRCTL) endif() @@ -351,6 +330,34 @@ if(DPP_CORO) execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.." COMMAND php buildtools/make_struct.php "\\Dpp\\Generator\\CoroGenerator") endif() + +if (DPP_BUILD_TEST) + enable_testing(${CMAKE_CURRENT_SOURCE_DIR}/..) + file(GLOB testnamelist "${CMAKE_CURRENT_SOURCE_DIR}/../src/*") + foreach (fulltestname ${testnamelist}) + get_filename_component(testname ${fulltestname} NAME) + if (NOT "${testname}" STREQUAL "dpp") + message("-- Configuring test: ${Green}${testname}${ColourReset} with source: ${modules_dir}/${testname}/*.cpp") + set (testsrc "") + file(GLOB testsrc "${modules_dir}/${testname}/*.cpp") + add_executable(${testname} ${testsrc}) + if (DPP_CORO) + target_compile_features(${testname} PRIVATE cxx_std_20) + else() + target_compile_features(${testname} PRIVATE cxx_std_17) + endif() + if (MSVC) + target_compile_options(${testname} PRIVATE /utf-8) + endif() + target_link_libraries(${testname} PUBLIC ${modname}) + endif() + endforeach() + add_test( + NAME unittests + COMMAND library/unittest + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/library + ) +endif() if(WIN32 AND NOT MINGW) if (NOT WINDOWS_32_BIT) diff --git a/src/unittest/coro.cpp b/src/unittest/coro.cpp new file mode 100644 index 0000000000..1c6357b898 --- /dev/null +++ b/src/unittest/coro.cpp @@ -0,0 +1,490 @@ +#include +#include +#include + +#include "test.h" + +#ifdef DPP_CORO + +namespace { + +namespace std_coroutine = dpp::detail::std_coroutine; + +template +struct test_exception : std::exception { +}; + +std::atomic exceptions_caught = 0; + +std::array, 10> job_data; +std::array, 10> task_data; + + +struct simple_awaitable { + test_t &test_for; + volatile int value{}; + int result{}; + + bool await_ready() const noexcept { + return false; + } + + template + void await_suspend(std_coroutine::coroutine_handle handle) { + std::thread th([this, handle]() mutable { + auto *test = &test_for; + std::this_thread::sleep_for(std::chrono::seconds(5)); + result = value; + try { + handle.resume(); + } catch (const std::exception &) { + /* no exception should be caught */ + set_status(*test, ts_failed, "unexpected exception during resume"); + } + }); + th.detach(); + } + + int await_resume() const noexcept { + return result; + } +}; + +void sync_awaitable_fun(std::function callback) { + callback(42); +} + +void sync_awaitable_throw(std::function callback) { + throw test_exception<0>{}; +} + +void async_awaitable_wait5(std::function callback) { + std::thread th([cc = std::move(callback)]() noexcept { + std::this_thread::sleep_for(std::chrono::seconds(5)); + cc(69); + }); + th.detach(); +} + +struct job_awaitable { + volatile int value{}; + int result{}; + + bool await_ready() const noexcept { + return false; + } + + template + void await_suspend(std_coroutine::coroutine_handle handle) { + std::thread th([this, handle]() mutable { + std::this_thread::sleep_for(std::chrono::seconds(5)); + result = value; + try { + handle.resume(); + } catch (const test_exception<42> &) { + /* intended exception to catch. we should have 2, one for the job_data test, one for the top-level job test */ + int exceptions = ++exceptions_caught; + if (exceptions == 2) + set_status(CORO_JOB_OFFLINE, ts_success); + else if (exceptions > 2) + set_status(CORO_JOB_OFFLINE, ts_failed, "resume() threw more than expected"); + } catch (const std::exception &) { + /* anything else should not be caught */ + set_status(CORO_JOB_OFFLINE, ts_failed, "resume() threw an exception it shouldn't have"); + } + }); + th.detach(); + } + + int await_resume() const noexcept { + return result; + } +}; + +dpp::job job_offline_test() { + if (int ret = co_await job_awaitable{42}; ret != 42) + set_status(CORO_JOB_OFFLINE, ts_failed, "failed simple awaitable"); + std::array jobs; + for (int i = 0; i < 10; ++i) { + jobs[i] = [](int i) -> dpp::job { + static std::atomic passed = false; + if (int ret = co_await job_awaitable{i}; ret != i) + set_status(CORO_JOB_OFFLINE, ts_failed, "failed in-loop awaitable"); + job_data[i] = i; + for (int j = 0; j < 10; ++j) { + if (job_data[j] != j) + co_return; + } + if (passed.exchange(true) == true) // another thread came here and already passed this check, skip the exception + co_return; + throw test_exception<42>(); // should be caught by simple_awaitable (since this is after resume) + }(i); + } + if (std::find_if(job_data.begin(), job_data.end(), [](int i){ return i > 0; }) != job_data.end()) { + set_status(CORO_JOB_OFFLINE, ts_failed, "job should not have modified the data at this point"); + } + + // verify that exceptions work as expected (dpp::job throws uncaught exceptions immediately to the caller / resumer) + try { + []() -> dpp::job { + throw test_exception<1>{}; + }(); + } catch (const test_exception<1> &) { + throw test_exception<42>{}; // should be caught by simple_awaitable (since this is after resume) + } +} + +dpp::task task_offline_test() { + namespace time = std::chrono; + using clock = time::system_clock; + static auto &test = CORO_TASK_OFFLINE; + + if (int ret = co_await simple_awaitable{test, 42}; ret != 42) + set_status(test, ts_failed, "failed simple awaitable"); + std::array, 10> tasks; + auto start = clock::now(); + for (int i = 0; i < 10; ++i) { + tasks[i] = [](int i) -> dpp::task { + if (int ret = co_await simple_awaitable{test, i}; ret != i) + set_status(test, ts_failed, "failed in-loop awaitable"); + job_data[i] = i; + co_return i; + }(i); + } + for (int i = 0; i < 10; ++i) { + if (co_await tasks[i] != i) + set_status(test, ts_failed, "failed to co_await expected value"); + } + auto diff = clock::now() - start; + if (diff > time::seconds(10)) { // task is async so 10 parallel tasks should all take 5 seconds + some overhead + set_status(test, ts_failed, "parallel tasks took longer than expected"); + } + + // verify that exceptions work as expected (dpp::task throws uncaught exceptions to co_await-er) + dpp::task task1; + dpp::task task2; + try { + task1 = []() -> dpp::task { // throws after co_await + if (int ret = co_await simple_awaitable{test, 69}; ret != 69) + set_status(test, ts_failed, "nested failed simple awaitable"); + dpp::task task2 = []() -> dpp::task { + co_await std_coroutine::suspend_never{}; // needed to make sure the task is initialized properly + throw test_exception<1>{}; + }(); + co_await task2; + }(); + task2 = []() -> dpp::task { // throws immediately + co_await std_coroutine::suspend_never{}; // needed to make sure the task is initialized properly + throw test_exception<2>{}; + }(); + } catch (const std::exception &e) { + /* SHOULD NOT throw. exceptions are sent when resuming from co_await */ + set_status(test, ts_failed, "task threw in constructor"); + } + + bool success = false; + // test throw after co_await + try { + co_await task1; + } catch (const test_exception<1> &) { + success = true; + } + if (!success) + set_status(CORO_TASK_OFFLINE, ts_failed, "task failed to throw expected test_exception<1>"); + success = false; + // test throw without suspending + try { + co_await task2; + } + catch (const test_exception<2> &) { + success = true; + } + if (!success) + set_status(CORO_TASK_OFFLINE, ts_failed, "task failed to throw expected test_exception<2>"); + // test cancel (throws dpp::task_cancelled_exception) + auto task3 = []() -> dpp::task { + auto make_task = []() -> dpp::task { + for (int i = 0; i < 5; ++i) { + co_await dpp::async(&async_awaitable_wait5); + } + set_status(CORO_TASK_OFFLINE, ts_failed, "failed to cancel nested task"); + }; + std::array, 3> tasks; + + // test cancel and catch { + auto start_time = clock::now(); + int exceptions = 0; + std::generate(tasks.begin(), tasks.end(), make_task); + for (auto &task : tasks) + task.cancel(); + for (auto &task : tasks) { + try { + co_await task; + } + catch (const dpp::task_cancelled_exception &) { + ++exceptions; + } + } + if (exceptions < 3 || !(clock::now() - start_time < std::chrono::seconds(10))) + set_status(CORO_TASK_OFFLINE, ts_failed, "failed to cancel 3 nested tasks in time"); + // } + + // test cancel and propagate { + std::generate(tasks.begin(), tasks.end(), make_task); + tasks[0].cancel(); + for (auto &task : tasks) { + co_await task; // tasks[0] should throw + } + // unreachable normally + set_status(CORO_TASK_OFFLINE, ts_failed, "failed to exit the scope after expected dpp::task_cancelled_exception"); + // } + }(); + try { + co_await task3; + } + catch (const dpp::task_cancelled_exception &) { + success = true; + } + if (!success) { + set_status(CORO_TASK_OFFLINE, ts_failed, "failed to propagate dpp::task_cancelled_exception to caller"); + } + throw test_exception<0>{}; +} + +dpp::coroutine coroutine_offline_test() { + static auto &test = CORO_COROUTINE_OFFLINE; + std::array data; + int num = 0; + + auto factory = [&data](int &i) -> dpp::coroutine { + if (int ret = co_await simple_awaitable{test, 42}; ret != 42) + set_status(test, ts_failed, "failed simple awaitable"); + data[i] = i; + co_return i++; + }; + if (int ret = co_await factory(num); ret != 0) + set_status(test, ts_failed, "coroutine 1 to set expected values"); + if (int ret = co_await factory(num); ret != 1) + set_status(test, ts_failed, "coroutine 2 to set expected values"); + if (int ret = co_await factory(num); ret != 2) + set_status(test, ts_failed, "coroutine 3 to set expected values"); + if (data != std::to_array({0, 1, 2})) + set_status(test, ts_failed, "unexpected test data"); + + // verify that exceptions work as expected (dpp::coroutine throws uncaught exceptions to co_await-er) + co_await []() -> dpp::coroutine { + dpp::coroutine nested1; + dpp::coroutine nested2; + try { + nested1 = []() -> dpp::coroutine { + int n = rand(); + if (int ret = co_await simple_awaitable{test, n}; ret != n) { + set_status(test, ts_failed, "nested failed simple awaitable"); + } + throw test_exception<1>{}; + }(); + nested2 = []() -> dpp::coroutine { + co_await std_coroutine::suspend_never{}; + throw test_exception<2>{}; + }(); + } catch (const std::exception &e) { + /* SHOULD NOT throw. exceptions are sent when resuming from co_await */ + set_status(test, ts_failed, "threw before co_await"); + } + bool success1 = false; + bool success2 = false; + try { + co_await nested1; + } catch (const test_exception<1> &) { + success1 = true; + } + try { + co_await nested2; + } catch (const test_exception<2> &) { + success2 = true; + } + if (success1 && success2) + throw test_exception<0>{}; + else + set_status(test, ts_failed, "failed to throw at co_await"); + }(); // test_exception<0> escapes +} + +dpp::job async_test() { + namespace time = std::chrono; + using clock = time::system_clock; + test_t &test = CORO_ASYNC_OFFLINE; + try { + std::array, 10> arr; + + std::generate(arr.begin(), arr.end(), [] { return dpp::async{&sync_awaitable_fun}; }); + for (auto &async : arr) { + if (int ret = co_await async; ret != 42) + set_status(test, ts_failed, "failed to sync resume with expected value"); + } + bool success = false; + try { + dpp::async throws{&sync_awaitable_throw}; + } catch (const test_exception<0> &) { + success = true; + } + if (!success) + set_status(test, ts_failed, "failed to propagate exception"); + auto now = clock::now(); + std::generate(arr.begin(), arr.end(), [] { return dpp::async{&async_awaitable_wait5}; }); + for (auto &async : arr) { + if (int ret = co_await async; ret != 69) + set_status(test, ts_failed, "failed to async resume with expected value"); + } + auto diff = clock::now() - now; + if (diff > time::seconds(10)) { + // async executes asynchronously so we should be waiting 5 seconds + some overhead + set_status(test, ts_failed, "parallel asyncs took more time than expected"); + } + set_status(test, ts_success); + } catch (const std::exception &e) { + /* no exception should be caught here */ + set_status(test, ts_failed, "unknown exception thrown"); + } +} + +} + +void coro_offline_tests() +{ + start_test(CORO_JOB_OFFLINE); + // Initialize all job data to -1 + std::fill(job_data.begin(), job_data.end(), -1); + job_offline_test(); + + start_test(CORO_TASK_OFFLINE); + std::fill(task_data.begin(), task_data.end(), -1); + []() -> dpp::job { + dpp::task task = task_offline_test(); + + try { + co_await dpp::task{std::move(task)}; + } catch (const test_exception<0> &) { // exception thrown at the end of the task test + set_status(CORO_TASK_OFFLINE, ts_success); + } catch (const std::exception &e) { // anything else should not escape + set_status(CORO_TASK_OFFLINE, ts_failed, "unknown exception thrown"); + } + }(); + + start_test(CORO_COROUTINE_OFFLINE); + []() -> dpp::job { + dpp::coroutine task = coroutine_offline_test(); + + try { + co_await dpp::coroutine{std::move(task)}; + } catch (const test_exception<0> &) { + set_status(CORO_COROUTINE_OFFLINE, ts_success); + } catch (const std::exception &e) { // anything else should not escape + set_status(CORO_COROUTINE_OFFLINE, ts_failed, "unknown exception thrown"); + } + }(); + + start_test(CORO_ASYNC_OFFLINE); + async_test(); +} + +void event_handler_test(dpp::cluster *bot) { + bot->on_message_create.co_attach([](dpp::message_create_t event) -> dpp::job { + if (event.msg.content == "coro test") { + dpp::cluster *bot = event.from->creator; + + set_status(CORO_EVENT_HANDLER, ts_success); + start_test(CORO_API_CALLS); + dpp::confirmation_callback_t result = co_await bot->co_message_edit(dpp::message{event.msg}.set_content("coro 👍")); + dpp::message *confirm = std::get_if(&(result.value)); + set_status(CORO_API_CALLS, (confirm == nullptr || confirm->content != "coro 👍") ? ts_failed : ts_success); + + if (extended) { + start_test(CORO_MUMBO_JUMBO); + std::array, 3> tasks; + for (int i = 0; i < 3; ++i) { + tasks[i] = [](dpp::cluster *bot, dpp::snowflake channel, int i) -> dpp::task { + using user_member = std::pair, std::optional>; + constexpr auto get_member_user = [](dpp::cluster *bot) -> dpp::task { + std::pair, std::optional> ret{}; + dpp::confirmation_callback_t result; + + try { + ret.second = dpp::find_guild_member(TEST_GUILD_ID, TEST_USER_ID); + } catch (const dpp::cache_exception &e) {} + if (!ret.second.has_value()) { + result = co_await bot->co_guild_get_member(TEST_GUILD_ID, TEST_USER_ID); + if (!result.is_error()) { + ret.second = std::get(std::move(result).value); + } + } + result = co_await bot->co_user_get_cached(TEST_USER_ID); + if (!result.is_error()) { + ret.first = std::get(std::move(result).value); + } + co_return ret; + }; + auto get_member_task = get_member_user(bot); + std::string content = "coro " + std::to_string(i); + dpp::confirmation_callback_t result = co_await bot->co_message_create(dpp::message{channel, content}); + + if (result.is_error()) + co_return {}; + dpp::message msg = std::get(std::move(result).value); + if (msg.content != content) + co_return {}; + user_member pair = co_await get_member_task; + if (!pair.first.has_value()) + co_return {}; + const std::string& member_nick = pair.second.has_value() ? pair.second->nickname : ""; + const std::string& user_nick = pair.first->username; + result = co_await bot->co_message_edit(msg.set_content("coro " + (member_nick.empty() ? user_nick : member_nick) + " " + std::to_string(i))); + co_return result.is_error() ? dpp::snowflake{} : std::get(result.value).id; + }(bot, event.msg.channel_id, i); + } + std::array msg_ids; + std::array, 3> reacts; + for (int i = 0; i < 3; ++i) { + try { + msg_ids[i] = co_await tasks[i]; + + if (msg_ids[i] == dpp::snowflake{}) { + set_status(CORO_MUMBO_JUMBO, ts_failed, "failed to post message"); + reacts[i] = dpp::async{[](auto &&cc) { cc(dpp::confirmation_callback_t{}); }}; + } + else + reacts[i] = bot->co_message_add_reaction(msg_ids[i], event.msg.channel_id, "✅"); + } catch (const std::exception &e) { + set_status(CORO_MUMBO_JUMBO, ts_failed, "message task threw " + std::string{e.what()}); + reacts[i] = dpp::async{[](auto &&cc) { cc(dpp::confirmation_callback_t{}); }}; + } + } + for (int i = 0; i < 3; ++i) { + try { + dpp::confirmation_callback_t result = co_await reacts[i]; + } catch (const std::exception &e) { + set_status(CORO_MUMBO_JUMBO, ts_failed, "react task threw " + std::string{e.what()}); + } + } + for (int i = 0; i < 3; ++i) { + bot->message_delete(msg_ids[i], event.msg.channel_id); + } + set_status(CORO_MUMBO_JUMBO, ts_success); + } + } + co_return; + }); + bot->message_create(dpp::message{"coro test"}.set_channel_id(TEST_TEXT_CHANNEL_ID)); +} + +void coro_online_tests(dpp::cluster *bot) { + start_test(CORO_EVENT_HANDLER); + event_handler_test(bot); +} + +#else + +void coro_offline_tests() {} +void coro_online_tests(dpp::cluster *bot) {} + +#endif diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 938d4f188c..ed43199bb4 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -54,20 +54,20 @@ int main() {\n\ ```\n\ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; - set_test("COMPARISON", false); + set_test(COMPARISON, false); dpp::user u1; dpp::user u2; dpp::user u3; u1.id = u2.id = 666; u3.id = 777; - set_test("COMPARISON", u1 == u2 && u1 != u3); + set_test(COMPARISON, u1 == u2 && u1 != u3); - set_test("MD_ESC_1", false); - set_test("MD_ESC_2", false); + set_test(MD_ESC_1, false); + set_test(MD_ESC_2, false); std::string escaped1 = dpp::utility::markdown_escape(test_to_escape); std::string escaped2 = dpp::utility::markdown_escape(test_to_escape, true); - set_test("MD_ESC_1", escaped1 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ + set_test(MD_ESC_1, escaped1 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ ```cpp\n\ int main() {\n\ /* Comment */\n\ @@ -76,7 +76,7 @@ int main() {\n\ };\n\ ```\n\ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ `small *code* block`\n"); - set_test("MD_ESC_2", escaped2 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ + set_test(MD_ESC_2, escaped2 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ \\`\\`\\`cpp\n\ int main\\(\\) {\n\ /\\* Comment \\*/\n\ @@ -86,11 +86,11 @@ int main\\(\\) {\n\ \\`\\`\\`\n\ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* block\\`\n"); - set_test("URLENC", false); - set_test("URLENC", dpp::utility::url_encode("ABC123_+\\|$*/AAA[]😄") == "ABC123_%2B%5C%7C%24%2A%2FAAA%5B%5D%F0%9F%98%84"); + set_test(URLENC, false); + set_test(URLENC, dpp::utility::url_encode("ABC123_+\\|$*/AAA[]😄") == "ABC123_%2B%5C%7C%24%2A%2FAAA%5B%5D%F0%9F%98%84"); - set_test("BASE64ENC", false); - set_test("BASE64ENC", + set_test(BASE64ENC, false); + set_test(BASE64ENC, dpp::base64_encode(reinterpret_cast("a"), 1) == "YQ==" && dpp::base64_encode(reinterpret_cast("bc"), 2) == "YmM=" && dpp::base64_encode(reinterpret_cast("def"), 3) == "ZGVm" && @@ -101,7 +101,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b ); dpp::http_connect_info hci; - set_test("HOSTINFO", false); + set_test(HOSTINFO, false); hci = dpp::https_client::get_host_info("https://test.com:444"); bool hci_test = (hci.scheme == "https" && hci.hostname == "test.com" && hci.port == 444 && hci.is_ssl == true); @@ -121,9 +121,9 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b hci = dpp::https_client::get_host_info("test.com"); hci_test = hci_test && (hci.scheme == "http" && hci.hostname == "test.com" && hci.port == 80 && hci.is_ssl == false); - set_test("HOSTINFO", hci_test); + set_test(HOSTINFO, hci_test); - set_test("HTTPS", false); + set_test(HTTPS, false); if (!offline) { dpp::multipart_content multipart = dpp::https_client::build_multipart( "{\"content\":\"test\"}", {"test.txt", "blob.blob"}, {"ABCDEFGHI", "BLOB!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"}, {"text/plain", "application/octet-stream"} @@ -137,69 +137,65 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b ); std::string hdr1 = c.get_header("server"); std::string content1 = c.get_content(); - set_test("HTTPS", hdr1 == "cloudflare" && c.get_status() == 200); + set_test(HTTPS, hdr1 == "cloudflare" && c.get_status() == 200); } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; - set_test("HTTPS", false); + set_test(HTTPS, false); } } - set_test("HTTP", false); + set_test(HTTP, false); try { dpp::https_client c2("github.com", 80, "/", "GET", "", {}, true); std::string hdr2 = c2.get_header("location"); std::string content2 = c2.get_content(); - set_test("HTTP", hdr2 == "https://github.com/" && c2.get_status() == 301); + set_test(HTTP, hdr2 == "https://github.com/" && c2.get_status() == 301); } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; - set_test("HTTP", false); + set_test(HTTP, false); } - set_test("MULTIHEADER", false); + set_test(MULTIHEADER, false); try { dpp::https_client c2("www.google.com", 80, "/", "GET", "", {}, true); size_t count = c2.get_header_count("set-cookie"); size_t count_list = c2.get_header_list("set-cookie").size(); // Google sets a bunch of cookies when we start accessing it. - set_test("MULTIHEADER", c2.get_status() == 200 && count > 1 && count == count_list); + set_test(MULTIHEADER, c2.get_status() == 200 && count > 1 && count == count_list); } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; - set_test("MULTIHEADER", false); + set_test(MULTIHEADER, false); } std::vector testaudio = load_test_audio(); - set_test("READFILE", false); + set_test(READFILE, false); std::string rf_test = dpp::utility::read_file(SHARED_OBJECT); FILE* fp = fopen(SHARED_OBJECT, "rb"); fseek(fp, 0, SEEK_END); size_t off = (size_t)ftell(fp); fclose(fp); - set_test("READFILE", off == rf_test.length()); + set_test(READFILE, off == rf_test.length()); - set_test("TIMESTAMPTOSTRING", false); -#ifndef _WIN32 - set_test("TIMESTAMPTOSTRING", dpp::ts_to_string(1642611864) == "2022-01-19T17:04:24Z"); -#else - set_test("TIMESTAMPTOSTRING", true); -#endif + set_test(TIMESTAMPTOSTRING, false); + set_test(TIMESTAMPTOSTRING, dpp::ts_to_string(1642611864) == "2022-01-19T17:04:24Z"); - set_test("ROLE.COMPARE", false); + set_test(ROLE_COMPARE, false); dpp::role role_1, role_2; role_1.position = 1; role_2.position = 2; - set_test("ROLE.COMPARE", role_1 < role_2 && role_1 != role_2); + set_test(ROLE_COMPARE, role_1 < role_2 && role_1 != role_2); - set_test("WEBHOOK", false); + set_test(WEBHOOK, false); try { dpp::webhook test_wh("https://discord.com/api/webhooks/833047646548133537/ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE"); - set_test("WEBHOOK", (test_wh.token == "ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE") && (test_wh.id == dpp::snowflake(833047646548133537))); + set_test(WEBHOOK, (test_wh.token == "ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE") && (test_wh.id == dpp::snowflake(833047646548133537))); } catch (const dpp::exception&) { - set_test("WEBHOOK", false); + set_test(WEBHOOK, false); } { // test interaction_create_t::get_parameter @@ -209,7 +205,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b dpp::interaction_create_t interaction(&client, ""); /* Check the method with subcommands */ - set_test("GET_PARAMETER_WITH_SUBCOMMANDS", false); + set_test(GET_PARAMETER_WITH_SUBCOMMANDS, false); dpp::command_interaction cmd_data; // command cmd_data.type = dpp::ctxm_chat_input; @@ -241,10 +237,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b interaction.command.data = cmd_data; dpp::snowflake value1 = std::get(interaction.get_parameter("user")); - set_test("GET_PARAMETER_WITH_SUBCOMMANDS", value1 == dpp::snowflake(189759562910400512)); + set_test(GET_PARAMETER_WITH_SUBCOMMANDS, value1 == dpp::snowflake(189759562910400512)); /* Check the method without subcommands */ - set_test("GET_PARAMETER_WITHOUT_SUBCOMMANDS", false); + set_test(GET_PARAMETER_WITHOUT_SUBCOMMANDS, false); dpp::command_interaction cmd_data2; // command cmd_data2.type = dpp::ctxm_chat_input; @@ -259,15 +255,15 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b interaction.command.data = cmd_data2; int64_t value2 = std::get(interaction.get_parameter("number")); - set_test("GET_PARAMETER_WITHOUT_SUBCOMMANDS", value2 == 123456); + set_test(GET_PARAMETER_WITHOUT_SUBCOMMANDS, value2 == 123456); } { // test dpp::command_option_choice::fill_from_json - set_test("OPTCHOICE_DOUBLE", false); - set_test("OPTCHOICE_INT", false); - set_test("OPTCHOICE_BOOL", false); - set_test("OPTCHOICE_SNOWFLAKE", false); - set_test("OPTCHOICE_STRING", false); + set_test(OPTCHOICE_DOUBLE, false); + set_test(OPTCHOICE_INT, false); + set_test(OPTCHOICE_BOOL, false); + set_test(OPTCHOICE_SNOWFLAKE, false); + set_test(OPTCHOICE_STRING, false); json j; dpp::command_option_choice choice; j["value"] = 54.321; @@ -289,15 +285,15 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b j["value"] = "foobar"; choice.fill_from_json(&j); bool success_string = std::holds_alternative(choice.value); - set_test("OPTCHOICE_DOUBLE", success_double); - set_test("OPTCHOICE_INT", success_int && success_int2); - set_test("OPTCHOICE_BOOL", success_bool); - set_test("OPTCHOICE_SNOWFLAKE", success_snowflake); - set_test("OPTCHOICE_STRING", success_string); + set_test(OPTCHOICE_DOUBLE, success_double); + set_test(OPTCHOICE_INT, success_int && success_int2); + set_test(OPTCHOICE_BOOL, success_bool); + set_test(OPTCHOICE_SNOWFLAKE, success_snowflake); + set_test(OPTCHOICE_STRING, success_string); } { - set_test("PERMISSION_CLASS", false); + set_test(PERMISSION_CLASS, false); bool success = false; auto p = dpp::permission(); p = 16; @@ -310,9 +306,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b success = s == "5120" && success; json j; j["value"] = p; -#ifndef _WIN32 success = dpp::snowflake_not_null(&j, "value") == 5120 && success; -#endif p.set(0).add(~uint64_t{0}).remove(dpp::p_speak).set(dpp::p_administrator); success = !p.has(dpp::p_administrator, dpp::p_ban_members) && success; // must return false because they're not both set success = !p.has(dpp::p_administrator | dpp::p_ban_members) && success; @@ -338,7 +332,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }; constexpr auto constexpr_success = permission_test({~uint64_t{0}}); // test in constant evaluated success = permission_test({~uint64_t{0}}) && constexpr_success && success; // test at runtime - set_test("PERMISSION_CLASS", success); + set_test(PERMISSION_CLASS, success); } { // some dpp::user methods @@ -347,18 +341,18 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b user1.discriminator = 0001; user1.username = "brain"; - set_test("USER.GET_MENTION", false); - set_test("USER.GET_MENTION", user1.get_mention() == "<@189759562910400512>"); + set_test(USER_GET_MENTION, false); + set_test(USER_GET_MENTION, user1.get_mention() == "<@189759562910400512>"); - set_test("USER.FORMAT_USERNAME", false); - set_test("USER.FORMAT_USERNAME", user1.format_username() == "brain#0001"); + set_test(USER_FORMAT_USERNAME, false); + set_test(USER_FORMAT_USERNAME, user1.format_username() == "brain#0001"); - set_test("USER.GET_CREATION_TIME", false); - set_test("USER.GET_CREATION_TIME", (uint64_t)user1.get_creation_time() == 1465312605); + set_test(USER_GET_CREATION_TIME, false); + set_test(USER_GET_CREATION_TIME, (uint64_t)user1.get_creation_time() == 1465312605); } { // avatar size function - set_test("UTILITY.AVATAR_SIZE", false); + set_test(UTILITY_AVATAR_SIZE, false); bool success = false; success = dpp::utility::avatar_size(0).empty(); success = dpp::utility::avatar_size(16) == "?size=16" && success; @@ -366,11 +360,11 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b success = dpp::utility::avatar_size(4096) == "?size=4096" && success; success = dpp::utility::avatar_size(8192).empty() && success; success = dpp::utility::avatar_size(3000).empty() && success; - set_test("UTILITY.AVATAR_SIZE", success); + set_test(UTILITY_AVATAR_SIZE, success); } { // cdn endpoint url getter - set_test("UTILITY.CDN_ENDPOINT_URL_HASH", false); + set_test(UTILITY_CDN_ENDPOINT_URL_HASH, false); bool success = false; success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png }, "foobar/test", "", dpp::i_jpg, 0).empty(); success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png }, "foobar/test", "", dpp::i_png, 0) == "https://cdn.discordapp.com/foobar/test.png" && success; @@ -381,11 +375,11 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "12345", dpp::i_png, 0, true, true) == "https://cdn.discordapp.com/foobar/test/a_12345.gif" && success; success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "", dpp::i_png, 0, true, true) == "https://cdn.discordapp.com/foobar/test.gif" && success; success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "", dpp::i_gif, 0, false, false).empty() && success; - set_test("UTILITY.CDN_ENDPOINT_URL_HASH", success); + set_test(UTILITY_CDN_ENDPOINT_URL_HASH, success); } { // sticker url getter - set_test("STICKER.GET_URL", false); + set_test(STICKER_GET_URL, false); dpp::sticker s; s.format_type = dpp::sf_png; bool success = s.get_url().empty(); @@ -395,7 +389,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b success = s.get_url() == "https://cdn.discordapp.com/stickers/12345.gif" && success; s.format_type = dpp::sf_lottie; success = s.get_url() == "https://cdn.discordapp.com/stickers/12345.json" && success; - set_test("STICKER.GET_URL", success); + set_test(STICKER_GET_URL, success); } { // user url getter @@ -410,8 +404,8 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b auto user3 = user2; user3.flags |= dpp::u_animated_icon; - set_test("USER.GET_AVATAR_URL", false); - set_test("USER.GET_AVATAR_URL", + set_test(USER_GET_AVATAR_URL, false); + set_test(USER_GET_AVATAR_URL, dpp::user().get_avatar_url().empty() && user1.get_avatar_url() == dpp::utility::cdn_host + "/embed/avatars/1.png" && user2.get_avatar_url() == dpp::utility::cdn_host + "/avatars/189759562910400512/5532c6414c70765a28cf9448c117205f.png" && @@ -430,34 +424,34 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b dpp::emoji emoji; emoji.id = 825407338755653641; - set_test("EMOJI.GET_URL", false); - set_test("EMOJI.GET_URL", emoji.get_url() == dpp::utility::cdn_host + "/emojis/825407338755653641.png"); + set_test(EMOJI_GET_URL, false); + set_test(EMOJI_GET_URL, emoji.get_url() == dpp::utility::cdn_host + "/emojis/825407338755653641.png"); } { // channel methods - set_test("CHANNEL.SET_TYPE", false); + set_test(CHANNEL_SET_TYPE, false); dpp::channel c; c.set_flags(dpp::c_nsfw | dpp::c_video_quality_720p); c.set_type(dpp::CHANNEL_CATEGORY); bool before = c.is_category() && !c.is_forum(); c.set_type(dpp::CHANNEL_FORUM); bool after = !c.is_category() && c.is_forum(); - set_test("CHANNEL.SET_TYPE", before && after); + set_test(CHANNEL_SET_TYPE, before && after); - set_test("CHANNEL.GET_MENTION", false); + set_test(CHANNEL_GET_MENTION, false); c.id = 825411707521728511; - set_test("CHANNEL.GET_MENTION", c.get_mention() == "<#825411707521728511>"); + set_test(CHANNEL_GET_MENTION, c.get_mention() == "<#825411707521728511>"); } { // utility methods - set_test("UTILITY.ICONHASH", false); + set_test(UTILITY_ICONHASH, false); auto iconhash1 = dpp::utility::iconhash("a_5532c6414c70765a28cf9448c117205f"); - set_test("UTILITY.ICONHASH", iconhash1.first == 6139187225817019994 && + set_test(UTILITY_ICONHASH, iconhash1.first == 6139187225817019994 && iconhash1.second == 2940732121894297695 && iconhash1.to_string() == "5532c6414c70765a28cf9448c117205f" ); - set_test("UTILITY.MAKE_URL_PARAMETERS", false); + set_test(UTILITY_MAKE_URL_PARAMETERS, false); auto url_params1 = dpp::utility::make_url_parameters({ {"foo", 15}, {"bar", 7} @@ -466,48 +460,48 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b {"foo", "hello"}, {"bar", "two words"} }); - set_test("UTILITY.MAKE_URL_PARAMETERS", url_params1 == "?bar=7&foo=15" && url_params2 == "?bar=two%20words&foo=hello"); + set_test(UTILITY_MAKE_URL_PARAMETERS, url_params1 == "?bar=7&foo=15" && url_params2 == "?bar=two%20words&foo=hello"); - set_test("UTILITY.MARKDOWN_ESCAPE", false); + set_test(UTILITY_MARKDOWN_ESCAPE, false); auto escaped = dpp::utility::markdown_escape( "> this is a quote\n" "**some bold text**"); - set_test("UTILITY.MARKDOWN_ESCAPE", "\\>this is a quote\\n\\*\\*some bold text\\*\\*"); + set_test(UTILITY_MARKDOWN_ESCAPE, "\\>this is a quote\\n\\*\\*some bold text\\*\\*"); - set_test("UTILITY.TOKENIZE", false); + set_test(UTILITY_TOKENIZE, false); auto tokens = dpp::utility::tokenize("some Whitespace seperated Text to Tokenize", " "); std::vector expected_tokens = {"some", "Whitespace", "seperated", "Text", "to", "Tokenize"}; - set_test("UTILITY.TOKENIZE", tokens == expected_tokens); + set_test(UTILITY_TOKENIZE, tokens == expected_tokens); - set_test("UTILITY.URL_ENCODE", false); + set_test(UTILITY_URL_ENCODE, false); auto url_encoded = dpp::utility::url_encode("S2-^$1Nd+U!g'8+_??o?p-bla bla"); - set_test("UTILITY.URL_ENCODE", url_encoded == "S2-%5E%241Nd%2BU%21g%278%2B_%3F%3Fo%3Fp-bla%20bla"); + set_test(UTILITY_URL_ENCODE, url_encoded == "S2-%5E%241Nd%2BU%21g%278%2B_%3F%3Fo%3Fp-bla%20bla"); - set_test("UTILITY.SLASHCOMMAND_MENTION", false); + set_test(UTILITY_SLASHCOMMAND_MENTION, false); auto mention1 = dpp::utility::slashcommand_mention(123, "name"); auto mention2 = dpp::utility::slashcommand_mention(123, "name", "sub"); auto mention3 = dpp::utility::slashcommand_mention(123, "name", "group", "sub"); bool success = mention1 == "" && mention2 == "" && mention3 == ""; - set_test("UTILITY.SLASHCOMMAND_MENTION", success); + set_test(UTILITY_SLASHCOMMAND_MENTION, success); - set_test("UTILITY.CHANNEL_MENTION", false); + set_test(UTILITY_CHANNEL_MENTION, false); auto channel_mention = dpp::utility::channel_mention(123); - set_test("UTILITY.CHANNEL_MENTION", channel_mention == "<#123>"); + set_test(UTILITY_CHANNEL_MENTION, channel_mention == "<#123>"); - set_test("UTILITY.USER_MENTION", false); + set_test(UTILITY_USER_MENTION, false); auto user_mention = dpp::utility::user_mention(123); - set_test("UTILITY.USER_MENTION", user_mention == "<@123>"); + set_test(UTILITY_USER_MENTION, user_mention == "<@123>"); - set_test("UTILITY.ROLE_MENTION", false); + set_test(UTILITY_ROLE_MENTION, false); auto role_mention = dpp::utility::role_mention(123); - set_test("UTILITY.ROLE_MENTION", role_mention == "<@&123>"); + set_test(UTILITY_ROLE_MENTION, role_mention == "<@&123>"); - set_test("UTILITY.EMOJI_MENTION", false); + set_test(UTILITY_EMOJI_MENTION, false); auto emoji_mention1 = dpp::utility::emoji_mention("role1", 123, false); auto emoji_mention2 = dpp::utility::emoji_mention("role2", 234, true); auto emoji_mention3 = dpp::utility::emoji_mention("white_check_mark", 0, false); auto emoji_mention4 = dpp::utility::emoji_mention("white_check_mark", 0, true); - set_test("UTILITY.EMOJI_MENTION", + set_test(UTILITY_EMOJI_MENTION, emoji_mention1 == "<:role1:123>" && emoji_mention2 == "" && emoji_mention3 == ":white_check_mark:" && @@ -516,54 +510,58 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } #ifndef _WIN32 - set_test("TIMESTRINGTOTIMESTAMP", false); + set_test(TIMESTRINGTOTIMESTAMP, false); json tj; tj["t1"] = "2022-01-19T17:18:14.506000+00:00"; tj["t2"] = "2022-01-19T17:18:14+00:00"; uint32_t inTimestamp = 1642612694; - set_test("TIMESTRINGTOTIMESTAMP", (uint64_t)dpp::ts_not_null(&tj, "t1") == inTimestamp && (uint64_t)dpp::ts_not_null(&tj, "t2") == inTimestamp); + set_test(TIMESTRINGTOTIMESTAMP, (uint64_t)dpp::ts_not_null(&tj, "t1") == inTimestamp && (uint64_t)dpp::ts_not_null(&tj, "t2") == inTimestamp); #else - set_test("TIMESTRINGTOTIMESTAMP", true); + set_test(TIMESTRINGTOTIMESTAMP, true); #endif { - set_test("TS", false); + set_test(TS, false); dpp::managed m(189759562910400512); - set_test("TS", ((uint64_t) m.get_creation_time()) == 1465312605); + set_test(TS, ((uint64_t) m.get_creation_time()) == 1465312605); + } + + { + coro_offline_tests(); } std::vector test_image = load_test_image(); - set_test("PRESENCE", false); - set_test("CLUSTER", false); + set_test(PRESENCE, false); + set_test(CLUSTER, false); try { dpp::cluster bot(token, dpp::i_all_intents); bot.set_websocket_protocol(dpp::ws_etf); - set_test("CLUSTER", true); - set_test("CONNECTION", false); - set_test("GUILDCREATE", false); - set_test("ICONHASH", false); + set_test(CLUSTER, true); + set_test(CONNECTION, false); + set_test(GUILDCREATE, false); + set_test(ICONHASH, false); - set_test("MSGCOLLECT", false); + set_test(MSGCOLLECT, false); if (!offline) { /* Intentional leak: freed on unit test end */ [[maybe_unused]] message_collector* collect_messages = new message_collector(&bot, 25); } - set_test("JSON_PARSE_ERROR", false); + set_test(JSON_PARSE_ERROR, false); dpp::rest_request(&bot, "/nonexistent", "address", "", dpp::m_get, "", [](const dpp::confirmation_callback_t& e) { if (e.is_error() && e.get_error().code == 404) { - set_test("JSON_PARSE_ERROR", true); + set_test(JSON_PARSE_ERROR, true); } else { - set_test("JSON_PARSE_ERROR", false); + set_test(JSON_PARSE_ERROR, false); } }); dpp::utility::iconhash i; std::string dummyval("fcffffffffffff55acaaaaaaaaaaaa66"); i = dummyval; - set_test("ICONHASH", (i.to_string() == dummyval)); + set_test(ICONHASH, (i.to_string() == dummyval)); /* This ensures we test both protocols, as voice is json and shard is etf */ bot.set_websocket_protocol(dpp::ws_etf); @@ -582,11 +580,11 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b std::promise ready_promise; std::future ready_future = ready_promise.get_future(); bot.on_ready([&](const dpp::ready_t & event) { - set_test("CONNECTION", true); + set_test(CONNECTION, true); ready_promise.set_value(); - set_test("APPCOMMAND", false); - set_test("LOGGER", false); + set_test(APPCOMMAND, false); + set_test(LOGGER, false); bot.log(dpp::ll_info, "Test log message"); bot.guild_command_create(dpp::slashcommand().set_name("testcommand") @@ -596,43 +594,43 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b .add_localization("fr", "zut", "Ou est la salor dans Discord?"), TEST_GUILD_ID, [&](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { - set_test("APPCOMMAND", true); - set_test("DELCOMMAND", false); + set_test(APPCOMMAND, true); + set_test(DELCOMMAND, false); dpp::slashcommand s = std::get(callback.value); bot.guild_command_delete(s.id, TEST_GUILD_ID, [&](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { dpp::message test_message(TEST_TEXT_CHANNEL_ID, "test message"); - set_test("DELCOMMAND", true); - set_test("MESSAGECREATE", false); - set_test("MESSAGEEDIT", false); - set_test("MESSAGERECEIVE", false); + set_test(DELCOMMAND, true); + set_test(MESSAGECREATE, false); + set_test(MESSAGEEDIT, false); + set_test(MESSAGERECEIVE, false); test_message.add_file("no-mime", "test"); test_message.add_file("test.txt", "test", "text/plain"); test_message.add_file("test.png", std::string{test_image.begin(), test_image.end()}, "image/png"); bot.message_create(test_message, [&bot](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { - set_test("MESSAGECREATE", true); - set_test("REACT", false); + set_test(MESSAGECREATE, true); + set_test(REACT, false); dpp::message m = std::get(callback.value); - set_test("REACTEVENT", false); + set_test(REACTEVENT, false); bot.message_add_reaction(m.id, TEST_TEXT_CHANNEL_ID, "😄", [](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { - set_test("REACT", true); + set_test(REACT, true); } else { - set_test("REACT", false); + set_test(REACT, false); } }); - set_test("EDITEVENT", false); + set_test(EDITEVENT, false); bot.message_edit(dpp::message(m).set_content("test edit"), [](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { - set_test("MESSAGEEDIT", true); + set_test(MESSAGEEDIT, true); } }); } }); } else { - set_test("DELCOMMAND", false); + set_test(DELCOMMAND, false); } }); } @@ -646,72 +644,72 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b std::cout << "[" << std::fixed << std::setprecision(3) << (dpp::utility::time_f() - get_start_time()) << "]: [\u001b[36m" << dpp::utility::loglevel(event.severity) << "\u001b[0m] " << event.message << "\n"; } if (event.message == "Test log message") { - set_test("LOGGER", true); + set_test(LOGGER, true); } }); - set_test("RUNONCE", false); + set_test(RUNONCE, false); uint8_t runs = 0; for (int x = 0; x < 10; ++x) { if (dpp::run_once()) { runs++; } } - set_test("RUNONCE", (runs == 1)); + set_test(RUNONCE, (runs == 1)); bot.on_voice_ready([&](const dpp::voice_ready_t & event) { - set_test("VOICECONN", true); + set_test(VOICECONN, true); dpp::discord_voice_client* v = event.voice_client; - set_test("VOICESEND", false); + set_test(VOICESEND, false); if (v && v->is_ready()) { v->send_audio_raw((uint16_t*)testaudio.data(), testaudio.size()); } else { - set_test("VOICESEND", false); + set_test(VOICESEND, false); } }); bot.on_invite_create([](const dpp::invite_create_t &event) { auto &inv = event.created_invite; if (!inv.code.empty() && inv.channel_id == TEST_TEXT_CHANNEL_ID && inv.guild_id == TEST_GUILD_ID && inv.created_at != 0 && inv.max_uses == 100) { - set_test("INVITE_CREATE_EVENT", true); + set_test(INVITE_CREATE_EVENT, true); } }); bot.on_invite_delete([](const dpp::invite_delete_t &event) { auto &inv = event.deleted_invite; if (!inv.code.empty() && inv.channel_id == TEST_TEXT_CHANNEL_ID && inv.guild_id == TEST_GUILD_ID) { - set_test("INVITE_DELETE_EVENT", true); + set_test(INVITE_DELETE_EVENT, true); } }); bot.on_voice_buffer_send([&](const dpp::voice_buffer_send_t & event) { if (event.buffer_size == 0) { - set_test("VOICESEND", true); + set_test(VOICESEND, true); } }); - set_test("SYNC", false); + set_test(SYNC, false); if (!offline) { dpp::message m = dpp::sync(&bot, &dpp::cluster::message_create, dpp::message(TEST_TEXT_CHANNEL_ID, "TEST")); - set_test("SYNC", m.content == "TEST"); + set_test(SYNC, m.content == "TEST"); } bot.on_guild_create([&](const dpp::guild_create_t & event) { if (event.created->id == TEST_GUILD_ID) { - set_test("GUILDCREATE", true); + set_test(GUILDCREATE, true); if (event.presences.size() && event.presences.begin()->second.user_id > 0) { - set_test("PRESENCE", true); + set_test(PRESENCE, true); } dpp::guild* g = dpp::find_guild(TEST_GUILD_ID); - set_test("CACHE", false); + set_test(CACHE, false); if (g) { - set_test("CACHE", true); - set_test("VOICECONN", false); + set_test(CACHE, true); + set_test(VOICECONN, false); dpp::discord_client* s = bot.get_shard(0); s->connect_voice(g->id, TEST_VC_ID, false, false); } else { - set_test("CACHE", false); + set_test(CACHE, false); } } }); @@ -731,10 +729,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b void delete_message_if_done() { if (files_tested == std::array{true, true, true} && pin_tested && thread_tested) { - set_test("MESSAGEDELETE", false); + set_test(MESSAGEDELETE, false); bot.message_delete(message_id, channel_id, [](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { - set_test("MESSAGEDELETE", true); + set_test(MESSAGEDELETE, true); } }); } @@ -756,16 +754,16 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b assert(!files_tested[index]); files_tested[index] = true; if (files_tested == std::array{true, true, true}) { - set_test("MESSAGEFILE", files_success == std::array{true, true, true}); + set_test(MESSAGEFILE, files_success == std::array{true, true, true}); } delete_message_if_done(); } void test_threads(const dpp::message &message) { - set_test("THREAD_CREATE_MESSAGE", false); - set_test("THREAD_DELETE", false); - set_test("THREAD_DELETE_EVENT", false); + set_test(THREAD_CREATE_MESSAGE, false); + set_test(THREAD_DELETE, false); + set_test(THREAD_DELETE_EVENT, false); bot.thread_create_with_message("test", message.channel_id, message.id, 60, 60, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock(mutex); if (callback.is_error()) { @@ -774,9 +772,9 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b else { auto thread = callback.get(); thread_id = thread.id; - set_test("THREAD_CREATE_MESSAGE", true); + set_test(THREAD_CREATE_MESSAGE, true); bot.channel_delete(thread.id, [this](const dpp::confirmation_callback_t &callback) { - set_test("THREAD_DELETE", !callback.is_error()); + set_test(THREAD_DELETE, !callback.is_error()); set_thread_tested(); }); } @@ -784,7 +782,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } void test_files(const dpp::message &message) { - set_test("MESSAGEFILE", false); + set_test(MESSAGEFILE, false); if (message.attachments.size() == 3) { static constexpr auto check_mimetype = [](const auto &headers, std::string mimetype) { if (auto it = headers.find("content-type"); it != headers.end()) { @@ -830,16 +828,16 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_pin_tested(); return; } - set_test("MESSAGEPIN", false); - set_test("MESSAGEUNPIN", false); + set_test(MESSAGEPIN, false); + set_test(MESSAGEUNPIN, false); bot.message_pin(channel_id, message_id, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock(mutex); if (!callback.is_error()) { - set_test("MESSAGEPIN", true); + set_test(MESSAGEPIN, true); bot.message_unpin(TEST_TEXT_CHANNEL_ID, message_id, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock(mutex); if (!callback.is_error()) { - set_test("MESSAGEUNPIN", true); + set_test(MESSAGEUNPIN, true); } set_pin_tested(); }); @@ -947,7 +945,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b void set_event_tested(event_flag flag) { - if (events_tested_mask & flag) { + if (events_tested_mask & flag) { return; } events_tested_mask |= flag; @@ -987,14 +985,14 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (!edit_tested) { dpp::thread edit = thread; - set_test("THREAD_EDIT", false); - set_test("THREAD_UPDATE_EVENT", false); + set_test(THREAD_EDIT, false); + set_test(THREAD_UPDATE_EVENT, false); edit.name = "edited"; edit.metadata.locked = true; bot.thread_edit(edit, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock(mutex); if (!callback.is_error()) { - set_test("THREAD_EDIT", true); + set_test(THREAD_EDIT, true); } set_edit_tested(); }); @@ -1005,7 +1003,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b { std::lock_guard lock{mutex}; - set_test("THREAD_GET_ACTIVE", false); + set_test(THREAD_GET_ACTIVE, false); bot.threads_get_active(TEST_GUILD_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (!callback.is_error()) { @@ -1014,7 +1012,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b const auto &thread = thread_it->second.active_thread; const auto &member = thread_it->second.bot_member; if (thread.id == thread_id && member.has_value() && member->user_id == bot.me.id) { - set_test("THREAD_GET_ACTIVE", true); + set_test(THREAD_GET_ACTIVE, true); } } } @@ -1031,26 +1029,26 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_members_tested(); return; } - set_test("THREAD_MEMBER_ADD", false); - set_test("THREAD_MEMBER_GET", false); - set_test("THREAD_MEMBERS_GET", false); - set_test("THREAD_MEMBER_REMOVE", false); - set_test("THREAD_MEMBERS_ADD_EVENT", false); - set_test("THREAD_MEMBERS_REMOVE_EVENT", false); + set_test(THREAD_MEMBER_ADD, false); + set_test(THREAD_MEMBER_GET, false); + set_test(THREAD_MEMBERS_GET, false); + set_test(THREAD_MEMBER_REMOVE, false); + set_test(THREAD_MEMBERS_ADD_EVENT, false); + set_test(THREAD_MEMBERS_REMOVE_EVENT, false); bot.thread_member_add(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { set_members_tested(); return; } - set_test("THREAD_MEMBER_ADD", true); + set_test(THREAD_MEMBER_ADD, true); bot.thread_member_get(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { set_members_tested(); return; } - set_test("THREAD_MEMBER_GET", true); + set_test(THREAD_MEMBER_GET, true); bot.thread_members_get(thread_id, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { @@ -1062,11 +1060,11 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_members_tested(); return; } - set_test("THREAD_MEMBERS_GET", true); + set_test(THREAD_MEMBERS_GET, true); bot.thread_member_remove(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (!callback.is_error()) { - set_test("THREAD_MEMBER_REMOVE", true); + set_test(THREAD_MEMBER_REMOVE, true); } set_members_tested(); }); @@ -1085,12 +1083,12 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } std::lock_guard lock{mutex}; - set_test("THREAD_MESSAGE", false); - set_test("THREAD_MESSAGE_CREATE_EVENT", false); - set_test("THREAD_MESSAGE_EDIT_EVENT", false); - set_test("THREAD_MESSAGE_REACT_ADD_EVENT", false); - set_test("THREAD_MESSAGE_REACT_REMOVE_EVENT", false); - set_test("THREAD_MESSAGE_DELETE_EVENT", false); + set_test(THREAD_MESSAGE, false); + set_test(THREAD_MESSAGE_CREATE_EVENT, false); + set_test(THREAD_MESSAGE_EDIT_EVENT, false); + set_test(THREAD_MESSAGE_REACT_ADD_EVENT, false); + set_test(THREAD_MESSAGE_REACT_REMOVE_EVENT, false); + set_test(THREAD_MESSAGE_DELETE_EVENT, false); events_to_test_mask |= MESSAGE_CREATE; bot.message_create(dpp::message{"hello thread"}.set_channel_id(thread.id), [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; @@ -1133,7 +1131,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b events_abort(); return; } - set_test("THREAD_MESSAGE", true); + set_test(THREAD_MESSAGE, true); }); }); }); @@ -1159,7 +1157,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_thread_create([&](const dpp::thread_create_t &event) { if (event.created.name == "thread test") { - set_test("THREAD_CREATE_EVENT", true); + set_test(THREAD_CREATE_EVENT, true); thread_helper.run(event.created); } }); @@ -1169,47 +1167,47 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (event.msg.author.id == bot.me.id) { if (event.msg.content == "test message" && !message_tested) { message_tested = true; - set_test("MESSAGERECEIVE", true); + set_test(MESSAGERECEIVE, true); message_helper.run(event.msg); - set_test("MESSAGESGET", false); + set_test(MESSAGESGET, false); bot.messages_get(event.msg.channel_id, 0, event.msg.id, 0, 5, [](const dpp::confirmation_callback_t &cc){ if (!cc.is_error()) { dpp::message_map mm = std::get(cc.value); if (mm.size()) { - set_test("MESSAGESGET", true); - set_test("TIMESTAMP", false); + set_test(MESSAGESGET, true); + set_test(TIMESTAMP, false); dpp::message m = mm.begin()->second; if (m.sent > 0) { - set_test("TIMESTAMP", true); + set_test(TIMESTAMP, true); } else { - set_test("TIMESTAMP", false); + set_test(TIMESTAMP, false); } } else { - set_test("MESSAGESGET", false); + set_test(MESSAGESGET, false); } } else { - set_test("MESSAGESGET", false); + set_test(MESSAGESGET, false); } }); - set_test("MSGCREATESEND", false); + set_test(MSGCREATESEND, false); event.send("MSGCREATESEND", [&bot, ch_id = event.msg.channel_id] (const auto& cc) { if (!cc.is_error()) { dpp::message m = std::get(cc.value); if (m.channel_id == ch_id) { - set_test("MSGCREATESEND", true); + set_test(MSGCREATESEND, true); } else { bot.log(dpp::ll_debug, cc.http_info.body); - set_test("MSGCREATESEND", false); + set_test(MSGCREATESEND, false); } bot.message_delete(m.id, m.channel_id); } else { bot.log(dpp::ll_debug, cc.http_info.body); - set_test("MSGCREATESEND", false); + set_test(MSGCREATESEND, false); } }); } if (event.msg.channel_id == thread_helper.thread_id && event.msg.content == "hello thread") { - set_test("THREAD_MESSAGE_CREATE_EVENT", true); + set_test(THREAD_MESSAGE_CREATE_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_CREATE); } } @@ -1218,10 +1216,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_message_reaction_add([&](const dpp::message_reaction_add_t & event) { if (event.reacting_user.id == bot.me.id) { if (event.reacting_emoji.name == "😄") { - set_test("REACTEVENT", true); + set_test(REACTEVENT, true); } if (event.channel_id == thread_helper.thread_id && event.reacting_emoji.name == THREAD_EMOJI) { - set_test("THREAD_MESSAGE_REACT_ADD_EVENT", true); + set_test(THREAD_MESSAGE_REACT_ADD_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_REACT); } } @@ -1230,7 +1228,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_message_reaction_remove([&](const dpp::message_reaction_remove_t & event) { if (event.reacting_user_id == bot.me.id) { if (event.channel_id == thread_helper.thread_id && event.reacting_emoji.name == THREAD_EMOJI) { - set_test("THREAD_MESSAGE_REACT_REMOVE_EVENT", true); + set_test(THREAD_MESSAGE_REACT_REMOVE_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_REMOVE_REACT); } } @@ -1238,7 +1236,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_message_delete([&](const dpp::message_delete_t & event) { if (event.deleted->channel_id == thread_helper.thread_id) { - set_test("THREAD_MESSAGE_DELETE_EVENT", true); + set_test(THREAD_MESSAGE_DELETE_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_DELETE); } }); @@ -1248,10 +1246,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (event.msg.author == bot.me.id) { if (event.msg.content == "test edit" && !message_edit_tested) { message_edit_tested = true; - set_test("EDITEVENT", true); + set_test(EDITEVENT, true); } if (event.msg.channel_id == thread_helper.thread_id && event.msg.content == "hello thread?") { - set_test("THREAD_MESSAGE_EDIT_EVENT", true); + set_test(THREAD_MESSAGE_EDIT_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_EDIT); } } @@ -1259,33 +1257,34 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_thread_update([&](const dpp::thread_update_t &event) { if (event.updating_guild->id == TEST_GUILD_ID && event.updated.id == thread_helper.thread_id && event.updated.name == "edited") { - set_test("THREAD_UPDATE_EVENT", true); + set_test(THREAD_UPDATE_EVENT, true); } }); bot.on_thread_members_update([&](const dpp::thread_members_update_t &event) { if (event.updating_guild->id == TEST_GUILD_ID && event.thread_id == thread_helper.thread_id) { if (std::find_if(std::begin(event.added), std::end(event.added), is_owner) != std::end(event.added)) { - set_test("THREAD_MEMBERS_ADD_EVENT", true); + set_test(THREAD_MEMBERS_ADD_EVENT, true); } if (std::find_if(std::begin(event.removed_ids), std::end(event.removed_ids), is_owner) != std::end(event.removed_ids)) { - set_test("THREAD_MEMBERS_REMOVE_EVENT", true); + set_test(THREAD_MEMBERS_REMOVE_EVENT, true); } } }); bot.on_thread_delete([&](const dpp::thread_delete_t &event) { if (event.deleting_guild->id == TEST_GUILD_ID && event.deleted.id == message_helper.thread_id) { - set_test("THREAD_DELETE_EVENT", true); + set_test(THREAD_DELETE_EVENT, true); } }); // set to execute from this thread (main thread) after on_ready is fired auto do_online_tests = [&] { - set_test("GUILD_BAN_CREATE", false); - set_test("GUILD_BAN_GET", false); - set_test("GUILD_BANS_GET", false); - set_test("GUILD_BAN_DELETE", false); + coro_online_tests(&bot); + set_test(GUILD_BAN_CREATE, false); + set_test(GUILD_BAN_GET, false); + set_test(GUILD_BANS_GET, false); + set_test(GUILD_BAN_DELETE, false); if (!offline) { // some deleted discord accounts to test the ban stuff with... dpp::snowflake deadUser1(802670069523415057); @@ -1298,7 +1297,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (event.is_error()) { return; } - set_test("GUILD_BAN_CREATE", true); + set_test(GUILD_BAN_CREATE, true); // when created, continue with getting and deleting // get ban @@ -1306,7 +1305,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (!event.is_error()) { dpp::ban ban = event.get(); if (ban.user_id == deadUser1 && ban.reason == "ban reason one") { - set_test("GUILD_BAN_GET", true); + set_test(GUILD_BAN_GET, true); } } }); @@ -1326,7 +1325,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } } if (successCount == 2) { - set_test("GUILD_BANS_GET", true); + set_test(GUILD_BANS_GET, true); } } }); @@ -1338,7 +1337,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (!event.is_error()) { bot.guild_ban_delete(TEST_GUILD_ID, deadUser3, [](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test("GUILD_BAN_DELETE", true); + set_test(GUILD_BAN_DELETE, true); } }); } @@ -1350,27 +1349,27 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); } - set_test("REQUEST_GET_IMAGE", false); + set_test(REQUEST_GET_IMAGE, false); if (!offline) { bot.request("https://dpp.dev/DPP-Logo.png", dpp::m_get, [&bot](const dpp::http_request_completion_t &callback) { if (callback.status != 200) { return; } - set_test("REQUEST_GET_IMAGE", true); + set_test(REQUEST_GET_IMAGE, true); dpp::emoji emoji; emoji.load_image(callback.body, dpp::i_png); emoji.name = "dpp"; // emoji unit test with the requested image - set_test("EMOJI_CREATE", false); - set_test("EMOJI_GET", false); - set_test("EMOJI_DELETE", false); + set_test(EMOJI_CREATE, false); + set_test(EMOJI_GET, false); + set_test(EMOJI_DELETE, false); bot.guild_emoji_create(TEST_GUILD_ID, emoji, [&bot](const dpp::confirmation_callback_t &event) { if (event.is_error()) { return; } - set_test("EMOJI_CREATE", true); + set_test(EMOJI_CREATE, true); auto created = event.get(); bot.guild_emoji_get(TEST_GUILD_ID, created.id, [&bot, created](const dpp::confirmation_callback_t &event) { @@ -1379,12 +1378,12 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } auto fetched = event.get(); if (created.id == fetched.id && created.name == fetched.name && created.flags == fetched.flags) { - set_test("EMOJI_GET", true); + set_test(EMOJI_GET, true); } bot.guild_emoji_delete(TEST_GUILD_ID, fetched.id, [](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test("EMOJI_DELETE", true); + set_test(EMOJI_DELETE, true); } }); }); @@ -1392,22 +1391,22 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); } - set_test("INVITE_CREATE", false); - set_test("INVITE_GET", false); - set_test("INVITE_DELETE", false); + set_test(INVITE_CREATE, false); + set_test(INVITE_GET, false); + set_test(INVITE_DELETE, false); if (!offline) { dpp::channel channel; channel.id = TEST_TEXT_CHANNEL_ID; dpp::invite invite; invite.max_age = 0; invite.max_uses = 100; - set_test("INVITE_CREATE_EVENT", false); + set_test(INVITE_CREATE_EVENT, false); bot.channel_invite_create(channel, invite, [&bot, invite](const dpp::confirmation_callback_t &event) { if (event.is_error()) return; auto created = event.get(); if (!created.code.empty() && created.channel_id == TEST_TEXT_CHANNEL_ID && created.guild_id == TEST_GUILD_ID && created.inviter.id == bot.me.id) { - set_test("INVITE_CREATE", true); + set_test(INVITE_CREATE, true); } bot.invite_get(created.code, [&bot, created](const dpp::confirmation_callback_t &event) { @@ -1415,30 +1414,30 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b auto retrieved = event.get(); if (retrieved.code == created.code && retrieved.guild_id == created.guild_id && retrieved.channel_id == created.channel_id && retrieved.inviter.id == created.inviter.id) { if (retrieved.destination_guild.flags & dpp::g_community) - set_test("INVITE_GET", retrieved.expires_at == 0); + set_test(INVITE_GET, retrieved.expires_at == 0); else - set_test("INVITE_GET", true); + set_test(INVITE_GET, true); } else { - set_test("INVITE_GET", false); + set_test(INVITE_GET, false); } } else { - set_test("INVITE_GET", false); + set_test(INVITE_GET, false); } - set_test("INVITE_DELETE_EVENT", false); + set_test(INVITE_DELETE_EVENT, false); bot.invite_delete(created.code, [](const dpp::confirmation_callback_t &event) { - set_test("INVITE_DELETE", !event.is_error()); + set_test(INVITE_DELETE, !event.is_error()); }); }); }); } - set_test("AUTOMOD_RULE_CREATE", false); - set_test("AUTOMOD_RULE_GET", false); - set_test("AUTOMOD_RULE_GET_ALL", false); - set_test("AUTOMOD_RULE_DELETE", false); + set_test(AUTOMOD_RULE_CREATE, false); + set_test(AUTOMOD_RULE_GET, false); + set_test(AUTOMOD_RULE_GET_ALL, false); + set_test(AUTOMOD_RULE_DELETE, false); if (!offline) { dpp::automod_rule automodRule; automodRule.name = "automod rule (keyword type)"; @@ -1460,7 +1459,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b return; } auto rules = event.get(); - set_test("AUTOMOD_RULE_GET_ALL", true); + set_test(AUTOMOD_RULE_GET_ALL, true); for (const auto &rule: rules) { if (rule.second.trigger_type == dpp::amod_type_keyword) { // delete one automod rule of type KEYWORD before creating one to make space... @@ -1475,7 +1474,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } auto created = event.get(); if (created.name == automodRule.name) { - set_test("AUTOMOD_RULE_CREATE", true); + set_test(AUTOMOD_RULE_CREATE, true); } // get automod rule @@ -1489,13 +1488,13 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b retrieved.trigger_metadata.keywords == automodRule.trigger_metadata.keywords && retrieved.trigger_metadata.regex_patterns == automodRule.trigger_metadata.regex_patterns && retrieved.trigger_metadata.allow_list == automodRule.trigger_metadata.allow_list && retrieved.actions.size() == automodRule.actions.size()) { - set_test("AUTOMOD_RULE_GET", true); + set_test(AUTOMOD_RULE_GET, true); } // delete the automod rule bot.automod_rule_delete(TEST_GUILD_ID, retrieved.id, [](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test("AUTOMOD_RULE_DELETE", true); + set_test(AUTOMOD_RULE_DELETE, true); } }); }); @@ -1503,16 +1502,16 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); } - set_test("USER_GET", false); - set_test("USER_GET_FLAGS", false); + set_test(USER_GET, false); + set_test(USER_GET_FLAGS, false); if (!offline) { bot.user_get(TEST_USER_ID, [](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { auto u = std::get(event.value); if (u.id == TEST_USER_ID) { - set_test("USER_GET", true); + set_test(USER_GET, true); } else { - set_test("USER_GET", false); + set_test(USER_GET, false); } json j = json::parse(event.http_info.body); uint64_t raw_flags = j["public_flags"]; @@ -1539,20 +1538,20 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b u.is_bot_http_interactions() == ((raw_flags & (1 << 19)) != 0) && u.is_active_developer() == ((raw_flags & (1 << 22)) != 0) ) { - set_test("USER_GET_FLAGS", true); + set_test(USER_GET_FLAGS, true); } else { - set_test("USER_GET_FLAGS", false); + set_test(USER_GET_FLAGS, false); } } else { - set_test("USER_GET", false); - set_test("USER_GET_FLAGS", false); + set_test(USER_GET, false); + set_test(USER_GET_FLAGS, false); } }); } - set_test("VOICE_CHANNEL_CREATE", false); - set_test("VOICE_CHANNEL_EDIT", false); - set_test("VOICE_CHANNEL_DELETE", false); + set_test(VOICE_CHANNEL_CREATE, false); + set_test(VOICE_CHANNEL_EDIT, false); + set_test(VOICE_CHANNEL_DELETE, false); if (!offline) { dpp::channel channel1; channel1.set_type(dpp::CHANNEL_VOICE) @@ -1564,14 +1563,14 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b try { createdChannel = bot.channel_create_sync(channel1); } catch (dpp::rest_exception &exception) { - set_test("VOICE_CHANNEL_CREATE", false); + set_test(VOICE_CHANNEL_CREATE, false); } if (createdChannel.name == channel1.name && createdChannel.user_limit == 99 && createdChannel.name == "voice1") { for (auto overwrite: createdChannel.permission_overwrites) { if (overwrite.id == TEST_GUILD_ID && overwrite.type == dpp::ot_role && overwrite.deny == dpp::p_view_channel) { - set_test("VOICE_CHANNEL_CREATE", true); + set_test(VOICE_CHANNEL_CREATE, true); } } @@ -1587,25 +1586,25 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b try { dpp::channel edited = bot.channel_edit_sync(createdChannel); if (edited.name == "foobar2" && edited.user_limit == 2) { - set_test("VOICE_CHANNEL_EDIT", true); + set_test(VOICE_CHANNEL_EDIT, true); } } catch (dpp::rest_exception &exception) { - set_test("VOICE_CHANNEL_EDIT", false); + set_test(VOICE_CHANNEL_EDIT, false); } // delete the voice channel try { bot.channel_delete_sync(createdChannel.id); - set_test("VOICE_CHANNEL_DELETE", true); + set_test(VOICE_CHANNEL_DELETE, true); } catch (dpp::rest_exception &exception) { - set_test("VOICE_CHANNEL_DELETE", false); + set_test(VOICE_CHANNEL_DELETE, false); } } } - set_test("FORUM_CREATION", false); - set_test("FORUM_CHANNEL_GET", false); - set_test("FORUM_CHANNEL_DELETE", false); + set_test(FORUM_CREATION, false); + set_test(FORUM_CHANNEL_GET, false); + set_test(FORUM_CHANNEL_DELETE, false); if (!offline) { dpp::channel c; c.name = "test-forum-channel"; @@ -1622,7 +1621,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b c.default_thread_rate_limit_per_user = 10; bot.channel_create(c, [&bot](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test("FORUM_CREATION", true); + set_test(FORUM_CREATION, true); auto channel = std::get(event.value); // retrieve the forum channel and check the values bot.channel_get(channel.id, [forum_id = channel.id, &bot](const dpp::confirmation_callback_t &event) { @@ -1638,56 +1637,56 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bool name = channel.name == "test-forum-channel"; bool sort = channel.default_sort_order == dpp::so_creation_date; bool rateLimit = channel.default_thread_rate_limit_per_user == 10; - set_test("FORUM_CHANNEL_GET", tag && name && sort && rateLimit); + set_test(FORUM_CHANNEL_GET, tag && name && sort && rateLimit); } else { - set_test("FORUM_CHANNEL_GET", false); + set_test(FORUM_CHANNEL_GET, false); } // delete the forum channel bot.channel_delete(forum_id, [](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test("FORUM_CHANNEL_DELETE", true); + set_test(FORUM_CHANNEL_DELETE, true); } else { - set_test("FORUM_CHANNEL_DELETE", false); + set_test(FORUM_CHANNEL_DELETE, false); } }); }); } else { - set_test("FORUM_CREATION", false); - set_test("FORUM_CHANNEL_GET", false); + set_test(FORUM_CREATION, false); + set_test(FORUM_CHANNEL_GET, false); } }); } - set_test("THREAD_CREATE", false); + set_test(THREAD_CREATE, false); if (!offline) { bot.thread_create("thread test", TEST_TEXT_CHANNEL_ID, 60, dpp::channel_type::CHANNEL_PUBLIC_THREAD, true, 60, [&](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { [[maybe_unused]] const auto &thread = event.get(); - set_test("THREAD_CREATE", true); + set_test(THREAD_CREATE, true); } // the thread tests are in the on_thread_create event handler }); } - set_test("MEMBER_GET", false); + set_test(MEMBER_GET, false); if (!offline) { bot.guild_get_member(TEST_GUILD_ID, TEST_USER_ID, [](const dpp::confirmation_callback_t &event){ if (!event.is_error()) { dpp::guild_member m = std::get(event.value); if (m.guild_id == TEST_GUILD_ID && m.user_id == TEST_USER_ID) { - set_test("MEMBER_GET", true); + set_test(MEMBER_GET, true); } else { - set_test("MEMBER_GET", false); + set_test(MEMBER_GET, false); } } else { - set_test("MEMBER_GET", false); + set_test(MEMBER_GET, false); } }); } - set_test("ROLE_CREATE", false); - set_test("ROLE_EDIT", false); - set_test("ROLE_DELETE", false); + set_test(ROLE_CREATE, false); + set_test(ROLE_EDIT, false); + set_test(ROLE_DELETE, false); if (!offline) { dpp::role r; r.guild_id = TEST_GUILD_ID; @@ -1702,10 +1701,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b createdRole.has_move_members() && createdRole.flags & dpp::r_mentionable && createdRole.colour == r.colour) { - set_test("ROLE_CREATE", true); + set_test(ROLE_CREATE, true); } } catch (dpp::rest_exception &exception) { - set_test("ROLE_CREATE", false); + set_test(ROLE_CREATE, false); } createdRole.guild_id = TEST_GUILD_ID; createdRole.name = "Test-Role-Edited"; @@ -1713,53 +1712,53 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b try { dpp::role edited = bot.role_edit_sync(createdRole); if (createdRole.id == edited.id && edited.name == "Test-Role-Edited") { - set_test("ROLE_EDIT", true); + set_test(ROLE_EDIT, true); } } catch (dpp::rest_exception &exception) { - set_test("ROLE_EDIT", false); + set_test(ROLE_EDIT, false); } try { bot.role_delete_sync(TEST_GUILD_ID, createdRole.id); - set_test("ROLE_DELETE", true); + set_test(ROLE_DELETE, true); } catch (dpp::rest_exception &exception) { - set_test("ROLE_DELETE", false); + set_test(ROLE_DELETE, false); } } }; - set_test("BOTSTART", false); + set_test(BOTSTART, false); try { if (!offline) { bot.start(true); - set_test("BOTSTART", true); + set_test(BOTSTART, true); } } catch (const std::exception &) { - set_test("BOTSTART", false); + set_test(BOTSTART, false); } - set_test("TIMERSTART", false); + set_test(TIMERSTART, false); uint32_t ticks = 0; dpp::timer th = bot.start_timer([&](dpp::timer timer_handle) { if (ticks == 5) { /* The simple test timer ticks every second. * If we get to 5 seconds, we know the timer is working. */ - set_test("TIMERSTART", true); + set_test(TIMERSTART, true); } ticks++; }, 1); - set_test("USER_GET_CACHED_PRESENT", false); + set_test(USER_GET_CACHED_PRESENT, false); try { dpp::user_identified u = bot.user_get_cached_sync(TEST_USER_ID); - set_test("USER_GET_CACHED_PRESENT", (u.id == TEST_USER_ID)); + set_test(USER_GET_CACHED_PRESENT, (u.id == TEST_USER_ID)); } catch (const std::exception&) { - set_test("USER_GET_CACHED_PRESENT", false); + set_test(USER_GET_CACHED_PRESENT, false); } - set_test("USER_GET_CACHED_ABSENT", false); + set_test(USER_GET_CACHED_ABSENT, false); try { /* This is the snowflake ID of a discord staff member. * We assume here that staffer's discord IDs will remain constant @@ -1768,38 +1767,38 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b * user ID. */ dpp::user_identified u = bot.user_get_cached_sync(90339695967350784); - set_test("USER_GET_CACHED_ABSENT", (u.id == dpp::snowflake(90339695967350784))); + set_test(USER_GET_CACHED_ABSENT, (u.id == dpp::snowflake(90339695967350784))); } catch (const std::exception&) { - set_test("USER_GET_CACHED_ABSENT", false); + set_test(USER_GET_CACHED_ABSENT, false); } - set_test("TIMEDLISTENER", false); + set_test(TIMEDLISTENER, false); dpp::timed_listener tl(&bot, 10, bot.on_log, [&](const dpp::log_t & event) { - set_test("TIMEDLISTENER", true); + set_test(TIMEDLISTENER, true); }); - set_test("ONESHOT", false); + set_test(ONESHOT, false); bool once = false; dpp::oneshot_timer ost(&bot, 5, [&](dpp::timer timer_handle) { if (!once) { - set_test("ONESHOT", true); + set_test(ONESHOT, true); } else { - set_test("ONESHOT", false); + set_test(ONESHOT, false); } once = true; }); - set_test("CUSTOMCACHE", false); + set_test(CUSTOMCACHE, false); dpp::cache testcache; test_cached_object_t* tco = new test_cached_object_t(666); tco->foo = "bar"; testcache.store(tco); test_cached_object_t* found_tco = testcache.find(666); if (found_tco && found_tco->id == dpp::snowflake(666) && found_tco->foo == "bar") { - set_test("CUSTOMCACHE", true); + set_test(CUSTOMCACHE, true); } else { - set_test("CUSTOMCACHE", false); + set_test(CUSTOMCACHE, false); } testcache.remove(found_tco); @@ -1809,36 +1808,36 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } } - noparam_api_test(current_user_get, dpp::user_identified, "CURRENTUSER"); - singleparam_api_test(channel_get, TEST_TEXT_CHANNEL_ID, dpp::channel, "GETCHAN"); - singleparam_api_test(guild_get, TEST_GUILD_ID, dpp::guild, "GETGUILD"); - singleparam_api_test_list(roles_get, TEST_GUILD_ID, dpp::role_map, "GETROLES"); - singleparam_api_test_list(channels_get, TEST_GUILD_ID, dpp::channel_map, "GETCHANS"); - singleparam_api_test_list(guild_get_invites, TEST_GUILD_ID, dpp::invite_map, "GETINVS"); - multiparam_api_test_list(guild_get_bans, TEST_GUILD_ID, dpp::ban_map, "GETBANS"); - singleparam_api_test_list(channel_pins_get, TEST_TEXT_CHANNEL_ID, dpp::message_map, "GETPINS"); - singleparam_api_test_list(guild_events_get, TEST_GUILD_ID, dpp::scheduled_event_map, "GETEVENTS"); - twoparam_api_test(guild_event_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::scheduled_event, "GETEVENT"); - twoparam_api_test_list(guild_event_users_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::event_member_map, "GETEVENTUSERS"); + noparam_api_test(current_user_get, dpp::user_identified, CURRENTUSER); + singleparam_api_test(channel_get, TEST_TEXT_CHANNEL_ID, dpp::channel, GETCHAN); + singleparam_api_test(guild_get, TEST_GUILD_ID, dpp::guild, GETGUILD); + singleparam_api_test_list(roles_get, TEST_GUILD_ID, dpp::role_map, GETROLES); + singleparam_api_test_list(channels_get, TEST_GUILD_ID, dpp::channel_map, GETCHANS); + singleparam_api_test_list(guild_get_invites, TEST_GUILD_ID, dpp::invite_map, GETINVS); + multiparam_api_test_list(guild_get_bans, TEST_GUILD_ID, dpp::ban_map, GETBANS); + singleparam_api_test_list(channel_pins_get, TEST_TEXT_CHANNEL_ID, dpp::message_map, GETPINS); + singleparam_api_test_list(guild_events_get, TEST_GUILD_ID, dpp::scheduled_event_map, GETEVENTS); + twoparam_api_test(guild_event_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::scheduled_event, GETEVENT); + twoparam_api_test_list(guild_event_users_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::event_member_map, GETEVENTUSERS); std::this_thread::sleep_for(std::chrono::seconds(20)); /* Test stopping timer */ - set_test("TIMERSTOP", false); - set_test("TIMERSTOP", bot.stop_timer(th)); + set_test(TIMERSTOP, false); + set_test(TIMERSTOP, bot.stop_timer(th)); - set_test("USERCACHE", false); + set_test(USERCACHE, false); if (!offline) { dpp::user* u = dpp::find_user(TEST_USER_ID); - set_test("USERCACHE", u); + set_test(USERCACHE, u); } - set_test("CHANNELCACHE", false); - set_test("CHANNELTYPES", false); + set_test(CHANNELCACHE, false); + set_test(CHANNELTYPES, false); if (!offline) { dpp::channel* c = dpp::find_channel(TEST_TEXT_CHANNEL_ID); dpp::channel* c2 = dpp::find_channel(TEST_VC_ID); - set_test("CHANNELCACHE", c && c2); - set_test("CHANNELTYPES", c && c->is_text_channel() && !c->is_voice_channel() && c2 && c2->is_voice_channel() && !c2->is_text_channel()); + set_test(CHANNELCACHE, c && c2); + set_test(CHANNELTYPES, c && c->is_text_channel() && !c->is_voice_channel() && c2 && c2->is_voice_channel() && !c2->is_text_channel()); } wait_for_tests(); @@ -1846,7 +1845,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } catch (const std::exception &e) { std::cout << e.what() << "\n"; - set_test("CLUSTER", false); + set_test(CLUSTER, false); } /* Return value = number of failed tests, exit code 0 = success */ diff --git a/src/unittest/test.h b/src/unittest/test.h index 6aba2a2110..2d14ef676b 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -36,27 +36,212 @@ _Pragma("warning( disable : 5105 )"); // 4251 warns when we export classes or st using json = nlohmann::json; -enum test_type_t { - /* A test that does not require discord connectivity */ - tt_offline, +enum test_flags_t { + tf_offline = 0, /* A test that requires discord connectivity */ - tt_online, - /* A test that requires both online and full tests to be enabled */ - tt_extended + tf_online = 1, + /* A test that requires full tests to be enabled */ + tf_extended = 1 << 1, + /* A test that requires coro to be enabled */ + tf_coro = 1 << 2 }; +enum test_status_t { + /* Test was not executed */ + ts_not_executed = 0, + /* Test was started */ + ts_started, + /* Test was skipped */ + ts_skipped, + /* Test succeeded */ + ts_success, + /* Test failed */ + ts_failed +}; + +struct test_t; + +inline std::vector tests = {}; + /* Represents a test case */ struct test_t { - /* Test type */ - test_type_t type; + std::string_view name; /* Description of test */ - std::string description; - /* Has been executed */ - bool executed = false; - /* Was successfully tested */ - bool success = false; + std::string_view description; + /* Test type */ + test_flags_t flags; + /* Test status */ + test_status_t status = ts_not_executed; + + test_t(std::string_view testname, std::string_view testdesc, int testflags); }; +#define DPP_TEST(name, desc, flags) inline test_t name = {#name, desc, flags} + +/* Current list of unit tests */ +DPP_TEST(CLUSTER, "Instantiate DPP cluster", tf_offline); +DPP_TEST(BOTSTART, "cluster::start method", tf_online); +DPP_TEST(CONNECTION, "Connection to client websocket", tf_online); +DPP_TEST(APPCOMMAND, "Creation of application command", tf_online); +DPP_TEST(DELCOMMAND, "Deletion of application command", tf_online); +DPP_TEST(LOGGER, "Log events", tf_online); +DPP_TEST(MESSAGECREATE, "Creation of a channel message", tf_online); +DPP_TEST(MESSAGEEDIT, "Editing a channel message", tf_online); +DPP_TEST(EDITEVENT, "Message edit event", tf_online); +DPP_TEST(MESSAGEDELETE, "Deletion of a channel message", tf_online); +DPP_TEST(MESSAGERECEIVE, "Receipt of a created message", tf_online); +DPP_TEST(MESSAGEFILE, "Message attachment send and check", tf_online); +DPP_TEST(CACHE, "Test guild cache", tf_online); +DPP_TEST(USERCACHE, "Test user cache", tf_online); +DPP_TEST(VOICECONN, "Connect to voice channel", tf_online); +DPP_TEST(VOICESEND, "Send audio to voice channel", tf_online); +DPP_TEST(REACT, "React to a message", tf_online); +DPP_TEST(REACTEVENT, "Reaction event", tf_online); +DPP_TEST(GUILDCREATE, "Receive guild create event", tf_online); +DPP_TEST(MESSAGESGET, "Get messages", tf_online); +DPP_TEST(TIMESTAMP, "crossplatform_strptime()", tf_online); +DPP_TEST(ICONHASH, "utility::iconhash", tf_offline); +DPP_TEST(CURRENTUSER, "cluster::current_user_get()", tf_online); +DPP_TEST(GETGUILD, "cluster::guild_get()", tf_online); +DPP_TEST(GETCHAN, "cluster::channel_get()", tf_online); +DPP_TEST(GETCHANS, "cluster::channels_get()", tf_online); +DPP_TEST(GETROLES, "cluster::roles_get()", tf_online); +DPP_TEST(GETINVS, "cluster::guild_get_invites()", tf_online); +DPP_TEST(GETBANS, "cluster::guild_get_bans()", tf_online); +DPP_TEST(GETPINS, "cluster::channel_pins_get()", tf_online); +DPP_TEST(GETEVENTS, "cluster::guild_events_get()", tf_online); +DPP_TEST(GETEVENT, "cluster::guild_event_get()", tf_online); +DPP_TEST(MSGCREATESEND, "message_create_t::send()", tf_online); +DPP_TEST(GETEVENTUSERS, "cluster::guild_event_users_get()", tf_online); +DPP_TEST(TIMERSTART, "start timer", tf_online); +DPP_TEST(TIMERSTOP, "stop timer", tf_online); +DPP_TEST(ONESHOT, "one-shot timer", tf_online); +DPP_TEST(TIMEDLISTENER, "timed listener", tf_online); +DPP_TEST(PRESENCE, "Presence intent", tf_online); +DPP_TEST(CUSTOMCACHE, "Instantiate a cache", tf_offline); +DPP_TEST(MSGCOLLECT, "message_collector", tf_online); +DPP_TEST(TS, "managed::get_creation_date()", tf_online); +DPP_TEST(READFILE, "utility::read_file()", tf_offline); +DPP_TEST(TIMESTAMPTOSTRING, "ts_to_string()", tf_offline); +DPP_TEST(TIMESTRINGTOTIMESTAMP, "ts_not_null()", tf_offline); +DPP_TEST(OPTCHOICE_DOUBLE, "command_option_choice::fill_from_json: double", tf_offline); +DPP_TEST(OPTCHOICE_INT, "command_option_choice::fill_from_json: int64_t", tf_offline); +DPP_TEST(OPTCHOICE_BOOL, "command_option_choice::fill_from_json: bool", tf_offline); +DPP_TEST(OPTCHOICE_SNOWFLAKE, "command_option_choice::fill_from_json: snowflake", tf_offline); +DPP_TEST(OPTCHOICE_STRING, "command_option_choice::fill_from_json: string", tf_offline); +DPP_TEST(HOSTINFO, "https_client::get_host_info()", tf_offline); +DPP_TEST(HTTPS, "https_client HTTPS request", tf_online); +DPP_TEST(HTTP, "https_client HTTP request", tf_offline); +DPP_TEST(MULTIHEADER, "multiheader cookie test", tf_offline); +DPP_TEST(RUNONCE, "run_once", tf_offline); +DPP_TEST(WEBHOOK, "webhook construct from URL", tf_offline); +DPP_TEST(MD_ESC_1, "Markdown escaping (ignore code block contents)", tf_offline); +DPP_TEST(MD_ESC_2, "Markdown escaping (escape code block contents)", tf_offline); +DPP_TEST(URLENC, "URL encoding", tf_offline); +DPP_TEST(BASE64ENC, "Base 64 encoding", tf_offline); +DPP_TEST(SYNC, "sync()", tf_online); +DPP_TEST(COMPARISON, "manged object comparison", tf_offline); +DPP_TEST(CHANNELCACHE, "find_channel()", tf_online); +DPP_TEST(CHANNELTYPES, "channel type flags", tf_online); +DPP_TEST(FORUM_CREATION, "create a forum channel", tf_online); +DPP_TEST(FORUM_CHANNEL_GET, "retrieve the created forum channel", tf_online); +DPP_TEST(FORUM_CHANNEL_DELETE, "delete the created forum channel", tf_online); + +DPP_TEST(GUILD_BAN_CREATE, "cluster::guild_ban_add ban three deleted discord accounts", tf_online); +DPP_TEST(GUILD_BAN_GET, "cluster::guild_get_ban getting one of the banned accounts", tf_online); +DPP_TEST(GUILD_BANS_GET, "cluster::guild_get_bans get bans using the after-parameter", tf_online); +DPP_TEST(GUILD_BAN_DELETE, "cluster::guild_ban_delete unban the banned discord accounts", tf_online); + +DPP_TEST(THREAD_CREATE, "cluster::thread_create", tf_online); +DPP_TEST(THREAD_CREATE_EVENT, "cluster::on_thread_create event", tf_online); +DPP_TEST(THREAD_DELETE, "cluster::channel_delete with thread", tf_online); +DPP_TEST(THREAD_DELETE_EVENT, "cluster::on_thread_delete event", tf_online); +DPP_TEST(THREAD_EDIT, "cluster::thread_edit", tf_online); +DPP_TEST(THREAD_UPDATE_EVENT, "cluster::on_thread_update event", tf_online); +DPP_TEST(THREAD_GET_ACTIVE, "cluster::threads_get_active", tf_online); + +DPP_TEST(VOICE_CHANNEL_CREATE, "creating a voice channel", tf_online); +DPP_TEST(VOICE_CHANNEL_EDIT, "editing the created voice channel", tf_online); +DPP_TEST(VOICE_CHANNEL_DELETE, "deleting the created voice channel", tf_online); + +DPP_TEST(PERMISSION_CLASS, "permission", tf_offline); +DPP_TEST(USER_GET, "cluster::user_get", tf_online); +DPP_TEST(USER_GET_FLAGS, "cluster::user_get flag parsing", tf_online); +DPP_TEST(MEMBER_GET, "cluster::guild_get_member", tf_online); +DPP_TEST(USER_GET_MENTION, "user::get_mention", tf_offline); +DPP_TEST(USER_FORMAT_USERNAME, "user::format_username", tf_offline); +DPP_TEST(USER_GET_CREATION_TIME, "user::get_creation_time", tf_offline); +DPP_TEST(USER_GET_AVATAR_URL, "user::get_avatar_url", tf_offline); +DPP_TEST(CHANNEL_SET_TYPE, "channel::set_type", tf_offline); +DPP_TEST(CHANNEL_GET_MENTION, "channel::get_mention", tf_offline); +DPP_TEST(UTILITY_ICONHASH, "utility::iconhash", tf_offline); +DPP_TEST(UTILITY_MAKE_URL_PARAMETERS, "utility::make_url_parameters", tf_offline); +DPP_TEST(UTILITY_MARKDOWN_ESCAPE, "utility::markdown_escape", tf_offline); +DPP_TEST(UTILITY_TOKENIZE, "utility::tokenize", tf_offline); +DPP_TEST(UTILITY_URL_ENCODE, "utility::url_encode", tf_offline); +DPP_TEST(UTILITY_SLASHCOMMAND_MENTION, "utility::slashcommand_mention", tf_offline); +DPP_TEST(UTILITY_CHANNEL_MENTION, "utility::channel_mention", tf_offline); +DPP_TEST(UTILITY_USER_MENTION, "utility::user_mention", tf_offline); +DPP_TEST(UTILITY_ROLE_MENTION, "utility::role_mention", tf_offline); +DPP_TEST(UTILITY_EMOJI_MENTION, "utility::emoji_mention", tf_offline); +DPP_TEST(UTILITY_AVATAR_SIZE, "utility::avatar_size", tf_offline); +DPP_TEST(UTILITY_CDN_ENDPOINT_URL_HASH, "utility::cdn_endpoint_url_hash", tf_offline); +DPP_TEST(STICKER_GET_URL, "sticker::get_url aka utility::cdn_endpoint_url_sticker", tf_offline); +DPP_TEST(EMOJI_GET_URL, "emoji::get_url", tf_offline); +DPP_TEST(ROLE_COMPARE, "role::operator<", tf_offline); +DPP_TEST(ROLE_CREATE, "cluster::role_create", tf_online); +DPP_TEST(ROLE_EDIT, "cluster::role_edit", tf_online); +DPP_TEST(ROLE_DELETE, "cluster::role_delete", tf_online); +DPP_TEST(JSON_PARSE_ERROR, "JSON parse error for post_rest", tf_online); +DPP_TEST(USER_GET_CACHED_PRESENT, "cluster::user_get_cached_sync() with present member", tf_online); +DPP_TEST(USER_GET_CACHED_ABSENT, "cluster::user_get_cached_sync() with not present member", tf_online); +DPP_TEST(GET_PARAMETER_WITH_SUBCOMMANDS, "interaction_create_t::get_parameter() with subcommands", tf_offline); +DPP_TEST(GET_PARAMETER_WITHOUT_SUBCOMMANDS, "interaction_create_t::get_parameter() without subcommands", tf_offline); +DPP_TEST(AUTOMOD_RULE_CREATE, "cluster::automod_rule_create", tf_online); +DPP_TEST(AUTOMOD_RULE_GET, "cluster::automod_rule_get", tf_online); +DPP_TEST(AUTOMOD_RULE_GET_ALL, "cluster::automod_rules_get", tf_online); +DPP_TEST(AUTOMOD_RULE_DELETE, "cluster::automod_rule_delete", tf_online); +DPP_TEST(REQUEST_GET_IMAGE, "using the cluster::request method to fetch an image", tf_online); +DPP_TEST(EMOJI_CREATE, "cluster::guild_emoji_create", tf_online); +DPP_TEST(EMOJI_GET, "cluster::guild_emoji_get", tf_online); +DPP_TEST(EMOJI_DELETE, "cluster::guild_emoji_delete", tf_online); +DPP_TEST(INVITE_CREATE_EVENT, "cluster::on_invite_create", tf_online); +DPP_TEST(INVITE_DELETE_EVENT, "cluster::on_invite_delete", tf_online); +DPP_TEST(INVITE_CREATE, "cluster::channel_invite_create", tf_online); +DPP_TEST(INVITE_GET, "cluster::invite_get", tf_online); +DPP_TEST(INVITE_DELETE, "cluster::invite_delete", tf_online); + +/* Extended set -- Less important, skipped on the master branch due to rate limits and GitHub actions limitations*/ +/* To execute, run unittests with "full" command line argument */ +DPP_TEST(MESSAGEPIN, "Pinning a channel message", tf_online | tf_extended); +DPP_TEST(MESSAGEUNPIN, "Unpinning a channel message", tf_online | tf_extended); + +DPP_TEST(THREAD_MEMBER_ADD, "cluster::thread_member_add", tf_online | tf_extended); +DPP_TEST(THREAD_MEMBER_GET, "cluster::thread_member_get", tf_online | tf_extended); +DPP_TEST(THREAD_MEMBERS_GET, "cluster::thread_members_get", tf_online | tf_extended); +DPP_TEST(THREAD_MEMBER_REMOVE, "cluster::thread_member_remove", tf_online | tf_extended); +DPP_TEST(THREAD_MEMBERS_ADD_EVENT, "cluster::on_thread_members_update event with member addition", tf_online | tf_extended); +DPP_TEST(THREAD_MEMBERS_REMOVE_EVENT, "cluster::on_thread_members_update event with member removal", tf_online | tf_extended); +DPP_TEST(THREAD_CREATE_MESSAGE, "cluster::thread_create_with_message", tf_online | tf_extended); + +DPP_TEST(THREAD_MESSAGE, "message manipulation in thread", tf_online | tf_extended); +DPP_TEST(THREAD_MESSAGE_CREATE_EVENT, "cluster::on_message_create in thread", tf_online | tf_extended); +DPP_TEST(THREAD_MESSAGE_EDIT_EVENT, "cluster::on_message_edit in thread", tf_online | tf_extended); +DPP_TEST(THREAD_MESSAGE_DELETE_EVENT, "cluster::on_message_delete in thread", tf_online | tf_extended); +DPP_TEST(THREAD_MESSAGE_REACT_ADD_EVENT, "cluster::on_reaction_add in thread", tf_online | tf_extended); +DPP_TEST(THREAD_MESSAGE_REACT_REMOVE_EVENT, "cluster::on_reaction_remove in thread", tf_online | tf_extended); + +DPP_TEST(CORO_JOB_OFFLINE, "coro: offline job", tf_offline | tf_coro); +DPP_TEST(CORO_COROUTINE_OFFLINE, "coro: offline coroutine", tf_offline | tf_coro); +DPP_TEST(CORO_TASK_OFFLINE, "coro: offline task", tf_offline | tf_coro); +DPP_TEST(CORO_ASYNC_OFFLINE, "coro: offline async", tf_offline | tf_coro); +DPP_TEST(CORO_EVENT_HANDLER, "coro: online event handler", tf_online | tf_coro); +DPP_TEST(CORO_API_CALLS, "coro: online api calls", tf_online | tf_coro); +DPP_TEST(CORO_MUMBO_JUMBO, "coro: online mumbo jumbo in event handler", tf_online | tf_coro | tf_extended); + +void coro_offline_tests(); +void coro_online_tests(dpp::cluster *bot); + class test_cached_object_t : public dpp::managed { public: test_cached_object_t(dpp::snowflake _id) : dpp::managed(_id) { }; @@ -78,7 +263,13 @@ extern dpp::snowflake TEST_EVENT_ID; /* True if we skip tt_online tests */ extern bool offline; +/* True if we skip tt_extended tests*/ extern bool extended; +#ifdef DPP_CORO +inline constexpr bool coro = true; +#else +inline constexpr bool coro = false; +#endif /** * @brief Perform a test of a REST base API call with one parameter @@ -209,16 +400,45 @@ extern bool extended; } /** - * @brief Sets a test's status - * - * @param testname test name (key) to set the status of + * @brief Sets a test's status (legacy) + * + * @param test The test to set the status of * @param success If set to true, sets success to true, if set to false and called * once, sets executed to true, if called twice, also sets success to false. * This means that before you run the test you should call this function once * with success set to false, then if/wen the test completes call it again with true. * If the test fails, call it a second time with false, or not at all. */ -void set_test(const std::string &testname, bool success = false); +void set_test(test_t &test, bool success = false); + +/** + * @brief Sets a test's status + * + * @param test The test to set the status of + * @param status Status to set the test to + */ +void set_status(test_t &test, test_status_t status, std::string_view message = {}); + +/** + * @brief Sets a test's status to ts_skipped + * + * @param test The test to set the status of + */ +void skip_test(test_t &test); + +/** + * @brief Sets a test's status to ts_started + * + * @param test The test to set the status of + */ +void start_test(test_t &test); + +/** + * @brief Check if a test is/should be skipped + * + * @return bool Whether the test is/should be skipped + */ +bool is_skipped(const test_t &test); /** * @brief Prints a summary of all tests executed @@ -278,7 +498,7 @@ class message_collector : public dpp::message_collector { message_collector(dpp::cluster* cl, uint64_t duration) : dpp::message_collector(cl, duration) { } virtual void completed(const std::vector& list) { - set_test("MSGCOLLECT", list.size() > 0); + set_test(MSGCOLLECT, list.size() > 0); } }; diff --git a/src/unittest/unittest.cpp b/src/unittest/unittest.cpp index bfbbcec7bc..37408448a9 100644 --- a/src/unittest/unittest.cpp +++ b/src/unittest/unittest.cpp @@ -23,160 +23,6 @@ #include #include -/* Current list of unit tests */ -std::map tests = { - {"CLUSTER", {tt_offline, "Instantiate DPP cluster", false, false}}, - {"BOTSTART", {tt_online, "cluster::start method", false, false}}, - {"CONNECTION", {tt_online, "Connection to client websocket", false, false}}, - {"APPCOMMAND", {tt_online, "Creation of application command", false, false}}, - {"DELCOMMAND", {tt_online, "Deletion of application command", false, false}}, - {"LOGGER", {tt_online, "Log events", false, false}}, - {"MESSAGECREATE", {tt_online, "Creation of a channel message", false, false}}, - {"MESSAGEEDIT", {tt_online, "Editing a channel message", false, false}}, - {"EDITEVENT", {tt_online, "Message edit event", false, false}}, - {"MESSAGEDELETE", {tt_online, "Deletion of a channel message", false, false}}, - {"MESSAGERECEIVE", {tt_online, "Receipt of a created message", false, false}}, - {"MESSAGEFILE", {tt_online, "Message attachment send and check", false, false}}, - {"CACHE", {tt_online, "Test guild cache", false, false}}, - {"USERCACHE", {tt_online, "Test user cache", false, false}}, - {"VOICECONN", {tt_online, "Connect to voice channel", false, false}}, - {"VOICESEND", {tt_online, "Send audio to voice channel", false, false}}, - {"REACT", {tt_online, "React to a message", false, false}}, - {"REACTEVENT", {tt_online, "Reaction event", false, false}}, - {"GUILDCREATE", {tt_online, "Receive guild create event", false, false}}, - {"MESSAGESGET", {tt_online, "Get messages", false, false}}, - {"TIMESTAMP", {tt_online, "crossplatform_strptime()", false, false}}, - {"ICONHASH", {tt_offline, "utility::iconhash", false, false}}, - {"CURRENTUSER", {tt_online, "cluster::current_user_get()", false, false}}, - {"GETGUILD", {tt_online, "cluster::guild_get()", false, false}}, - {"GETCHAN", {tt_online, "cluster::channel_get()", false, false}}, - {"GETCHANS", {tt_online, "cluster::channels_get()", false, false}}, - {"GETROLES", {tt_online, "cluster::roles_get()", false, false}}, - {"GETINVS", {tt_online, "cluster::guild_get_invites()", false, false}}, - {"GETBANS", {tt_online, "cluster::guild_get_bans()", false, false}}, - {"GETPINS", {tt_online, "cluster::channel_pins_get()", false, false}}, - {"GETEVENTS", {tt_online, "cluster::guild_events_get()", false, false}}, - {"GETEVENT", {tt_online, "cluster::guild_event_get()", false, false}}, - {"MSGCREATESEND", {tt_online, "message_create_t::send()", false, false}}, - {"GETEVENTUSERS", {tt_online, "cluster::guild_event_users_get()", false, false}}, - {"TIMERSTART", {tt_online, "start timer", false, false}}, - {"TIMERSTOP", {tt_online, "stop timer", false, false}}, - {"ONESHOT", {tt_online, "one-shot timer", false, false}}, - {"PRESENCE", {tt_online, "Presence intent", false, false}}, - {"CUSTOMCACHE", {tt_offline, "Instantiate a cache", false, false}}, - {"MSGCOLLECT", {tt_online, "message_collector", false, false}}, - {"TS", {tt_online, "managed::get_creation_date()", false, false}}, - {"READFILE", {tt_offline, "utility::read_file()", false, false}}, - {"TIMESTAMPTOSTRING", {tt_offline, "ts_to_string()", false, false}}, - {"TIMESTRINGTOTIMESTAMP", {tt_offline, "ts_not_null()", false, false}}, - {"OPTCHOICE_DOUBLE", {tt_offline, "command_option_choice::fill_from_json: double", false, false}}, - {"OPTCHOICE_INT", {tt_offline, "command_option_choice::fill_from_json: int64_t", false, false}}, - {"OPTCHOICE_BOOL", {tt_offline, "command_option_choice::fill_from_json: bool", false, false}}, - {"OPTCHOICE_SNOWFLAKE", {tt_offline, "command_option_choice::fill_from_json: snowflake", false, false}}, - {"OPTCHOICE_STRING", {tt_offline, "command_option_choice::fill_from_json: string", false, false}}, - {"HOSTINFO", {tt_offline, "https_client::get_host_info()", false, false}}, - {"HTTPS", {tt_online, "https_client HTTPS request", false, false}}, - {"HTTP", {tt_offline, "https_client HTTP request", false, false}}, - {"MULTIHEADER", {tt_offline, "multiheader cookie test", false, false}}, - {"RUNONCE", {tt_offline, "run_once", false, false}}, - {"WEBHOOK", {tt_offline, "webhook construct from URL", false, false}}, - {"MD_ESC_1", {tt_offline, "Markdown escaping (ignore code block contents)", false, false}}, - {"MD_ESC_2", {tt_offline, "Markdown escaping (escape code block contents)", false, false}}, - {"URLENC", {tt_offline, "URL encoding", false, false}}, - {"BASE64ENC", {tt_offline, "Base64 encoding", false, false}}, - {"SYNC", {tt_online, "sync()", false, false}}, - {"COMPARISON", {tt_offline, "manged object comparison", false, false}}, - {"CHANNELCACHE", {tt_online, "find_channel()", false, false}}, - {"CHANNELTYPES", {tt_online, "channel type flags", false, false}}, - {"FORUM_CREATION", {tt_online, "create a forum channel", false, false}}, - {"FORUM_CHANNEL_GET", {tt_online, "retrieve the created forum channel", false, false}}, - {"FORUM_CHANNEL_DELETE", {tt_online, "delete the created forum channel", false, false}}, - - {"GUILD_BAN_CREATE", {tt_online, "cluster::guild_ban_add ban three deleted discord accounts", false, false}}, - {"GUILD_BAN_GET", {tt_online, "cluster::guild_get_ban getting one of the banned accounts", false, false}}, - {"GUILD_BANS_GET", {tt_online, "cluster::guild_get_bans get bans using the after-parameter", false, false}}, - {"GUILD_BAN_DELETE", {tt_online, "cluster::guild_ban_delete unban the banned discord accounts", false, false}}, - - {"THREAD_CREATE", {tt_online, "cluster::thread_create", false, false}}, - {"THREAD_CREATE_EVENT", {tt_online, "cluster::on_thread_create event", false, false}}, - {"THREAD_DELETE", {tt_online, "cluster::channel_delete with thread", false, false}}, - {"THREAD_DELETE_EVENT", {tt_online, "cluster::on_thread_delete event", false, false}}, - {"THREAD_EDIT", {tt_online, "cluster::thread_edit", false, false}}, - {"THREAD_UPDATE_EVENT", {tt_online, "cluster::on_thread_update event", false, false}}, - {"THREAD_GET_ACTIVE", {tt_online, "cluster::threads_get_active", false, false}}, - - {"VOICE_CHANNEL_CREATE", {tt_online, "creating a voice channel", false, false}}, - {"VOICE_CHANNEL_EDIT", {tt_online, "editing the created voice channel", false, false}}, - {"VOICE_CHANNEL_DELETE", {tt_online, "deleting the created voice channel", false, false}}, - - {"PERMISSION_CLASS", {tt_offline, "permission", false, false}}, - {"USER_GET", {tt_online, "cluster::user_get", false, false}}, - {"USER_GET_FLAGS", {tt_online, "cluster::user_get flag parsing", false, false}}, - {"MEMBER_GET", {tt_online, "cluster::guild_get_member", false, false}}, - {"USER.GET_MENTION", {tt_offline, "user::get_mention", false, false}}, - {"USER.FORMAT_USERNAME", {tt_offline, "user::format_username", false, false}}, - {"USER.GET_CREATION_TIME", {tt_offline, "user::get_creation_time", false, false}}, - {"USER.GET_AVATAR_URL", {tt_offline, "user::get_avatar_url", false, false}}, - {"CHANNEL.SET_TYPE", {tt_offline, "channel::set_type", false, false}}, - {"CHANNEL.GET_MENTION", {tt_offline, "channel::get_mention", false, false}}, - {"UTILITY.ICONHASH", {tt_offline, "utility::iconhash", false, false}}, - {"UTILITY.MAKE_URL_PARAMETERS", {tt_offline, "utility::make_url_parameters", false, false}}, - {"UTILITY.MARKDOWN_ESCAPE", {tt_offline, "utility::markdown_escape", false, false}}, - {"UTILITY.TOKENIZE", {tt_offline, "utility::tokenize", false, false}}, - {"UTILITY.URL_ENCODE", {tt_offline, "utility::url_encode", false, false}}, - {"UTILITY.SLASHCOMMAND_MENTION", {tt_offline, "utility::slashcommand_mention", false, false}}, - {"UTILITY.CHANNEL_MENTION", {tt_offline, "utility::channel_mention", false, false}}, - {"UTILITY.USER_MENTION", {tt_offline, "utility::user_mention", false, false}}, - {"UTILITY.ROLE_MENTION", {tt_offline, "utility::role_mention", false, false}}, - {"UTILITY.EMOJI_MENTION", {tt_offline, "utility::emoji_mention", false, false}}, - {"UTILITY.AVATAR_SIZE", {tt_offline, "utility::avatar_size", false, false}}, - {"UTILITY.CDN_ENDPOINT_URL_HASH", {tt_offline, "utility::cdn_endpoint_url_hash", false, false}}, - {"STICKER.GET_URL", {tt_offline, "sticker::get_url aka utility::cdn_endpoint_url_sticker", false, false}}, - {"EMOJI.GET_URL", {tt_offline, "emoji::get_url", false, false}}, - {"ROLE.COMPARE", {tt_offline, "role::operator<", false, false}}, - {"ROLE_CREATE", {tt_online, "cluster::role_create", false, false}}, - {"ROLE_EDIT", {tt_online, "cluster::role_edit", false, false}}, - {"ROLE_DELETE", {tt_online, "cluster::role_delete", false, false}}, - {"JSON_PARSE_ERROR", {tt_online, "JSON parse error for post_rest", false, false}}, - {"USER_GET_CACHED_PRESENT", {tt_online, "cluster::user_get_cached_sync() with present member", false, false}}, - {"USER_GET_CACHED_ABSENT", {tt_online, "cluster::user_get_cached_sync() with not present member", false, false}}, - {"GET_PARAMETER_WITH_SUBCOMMANDS", {tt_offline, "interaction_create_t::get_parameter() with subcommands", false, false}}, - {"GET_PARAMETER_WITHOUT_SUBCOMMANDS", {tt_offline, "interaction_create_t::get_parameter() without subcommands", false, false}}, - {"AUTOMOD_RULE_CREATE", {tt_online, "cluster::automod_rule_create", false, false}}, - {"AUTOMOD_RULE_GET", {tt_online, "cluster::automod_rule_get", false, false}}, - {"AUTOMOD_RULE_GET_ALL", {tt_online, "cluster::automod_rules_get", false, false}}, - {"AUTOMOD_RULE_DELETE", {tt_online, "cluster::automod_rule_delete", false, false}}, - {"REQUEST_GET_IMAGE", {tt_online, "using the cluster::request method to fetch an image", false, false}}, - {"EMOJI_CREATE", {tt_online, "cluster::guild_emoji_create", false, false}}, - {"EMOJI_GET", {tt_online, "cluster::guild_emoji_get", false, false}}, - {"EMOJI_DELETE", {tt_online, "cluster::guild_emoji_delete", false, false}}, - {"INVITE_CREATE_EVENT", {tt_online, "cluster::on_invite_create", false, false}}, - {"INVITE_DELETE_EVENT", {tt_online, "cluster::on_invite_delete", false, false}}, - {"INVITE_CREATE", {tt_online, "cluster::channel_invite_create", false, false}}, - {"INVITE_GET", {tt_online, "cluster::invite_get", false, false}}, - {"INVITE_DELETE", {tt_online, "cluster::invite_delete", false, false}}, - - /* Extended set -- Less important, skipped on the master branch due to rate limits and GitHub actions limitations*/ - /* To execute, run unittests with "full" command line argument */ - {"MESSAGEPIN", {tt_extended, "Pinning a channel message", false, false}}, - {"MESSAGEUNPIN", {tt_extended, "Unpinning a channel message", false, false}}, - - {"THREAD_MEMBER_ADD", {tt_extended, "cluster::thread_member_add", false, false}}, - {"THREAD_MEMBER_GET", {tt_extended, "cluster::thread_member_get", false, false}}, - {"THREAD_MEMBERS_GET", {tt_extended, "cluster::thread_members_get", false, false}}, - {"THREAD_MEMBER_REMOVE", {tt_extended, "cluster::thread_member_remove", false, false}}, - {"THREAD_MEMBERS_ADD_EVENT", {tt_extended, "cluster::on_thread_members_update event with member addition", false, false}}, - {"THREAD_MEMBERS_REMOVE_EVENT", {tt_extended, "cluster::on_thread_members_update event with member removal", false, false}}, - {"THREAD_CREATE_MESSAGE", {tt_extended, "cluster::thread_create_with_message", false, false}}, - - {"THREAD_MESSAGE", {tt_extended, "message manipulation in thread", false, false}}, - {"THREAD_MESSAGE_CREATE_EVENT", {tt_extended, "cluster::on_message_create in thread", false, false}}, - {"THREAD_MESSAGE_EDIT_EVENT", {tt_extended, "cluster::on_message_edit in thread", false, false}}, - {"THREAD_MESSAGE_DELETE_EVENT", {tt_extended, "cluster::on_message_delete in thread", false, false}}, - {"THREAD_MESSAGE_REACT_ADD_EVENT", {tt_extended, "cluster::on_reaction_add in thread", false, false}}, - {"THREAD_MESSAGE_REACT_REMOVE_EVENT", {tt_extended, "cluster::on_reaction_remove in thread", false, false}}, -}; - double start = dpp::utility::time_f(); bool offline = false; bool extended = false; @@ -187,28 +33,60 @@ dpp::snowflake TEST_VC_ID = std::stoull(SAFE_GETENV("TEST_VC_ID")); dpp::snowflake TEST_USER_ID = std::stoull(SAFE_GETENV("TEST_USER_ID")); dpp::snowflake TEST_EVENT_ID = std::stoull(SAFE_GETENV("TEST_EVENT_ID")); -void set_test(const std::string &testname, bool success) { - auto i = tests.find(testname); - if (i != tests.end()) { - if (offline && i->second.type == tt_online) { - i->second.success = true; - i->second.executed = true; - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mSKIPPED\u001b[0m] " << i->second.description << "\n"; - } else { - if (!i->second.executed) { - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mTESTING\u001b[0m] " << i->second.description << "\n"; - } else if (!success) { - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[31mFAILED\u001b[0m] " << i->second.description << "\n"; - } - i->second.executed = true; - if (success) { - i->second.success = true; - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[32mSUCCESS\u001b[0m] " << i->second.description << "\n"; - } - } +test_t::test_t(std::string_view testname, std::string_view testdesc, int testflags) : name{testname}, description{testdesc}, flags{static_cast(testflags)} { + tests.push_back(this); +} + +void set_status(test_t &test, test_status_t newstatus, std::string_view message) { + static std::mutex m; + std::scoped_lock lock{m}; + + if (is_skipped(test) || newstatus == test.status) + return; + if (test.status != ts_failed) // disallow changing the state of a failed test, but we still log + test.status = newstatus; + switch (newstatus) { + case ts_started: + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mTESTING\u001b[0m] " << test.description << (message.empty() ? "" : " - " + std::string{message} ) << "\n"; + break; + + case ts_failed: + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[31mFAILED\u001b[0m] " << test.description << (message.empty() ? "" : " - " + std::string{message} ) << "\n"; + break; + + case ts_success: + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[32mSUCCESS\u001b[0m] " << test.description << (message.empty() ? "" : " - " + std::string{message} ) << "\n"; + break; + + case ts_skipped: + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mSKIPPED\u001b[0m] " << test.description << (message.empty() ? "" : " - " + std::string{message} ) << "\n"; + break; + + default: + break; } } +void start_test(test_t &test) { + set_status(test, ts_started); +} + +void skip_test(test_t &test) { + set_status(test, ts_skipped); +} + +void set_test(test_t &test, bool success) { + if (test.status == ts_not_executed) { + set_status(test, success ? ts_success : ts_started); + } else { + set_status(test, success ? ts_success : ts_failed); + } +} + +bool is_skipped(const test_t &test) { + return (test.flags & tf_online && offline) || (test.flags & tf_extended && !extended) || (test.flags & tf_coro && !coro); +} + double get_start_time() { return start; } @@ -222,18 +100,20 @@ int test_summary() { int failed = 0, passed = 0, skipped = 0; std::cout << "\u001b[37;1m\n\nUNIT TEST SUMMARY\n==================\n\u001b[0m"; for (auto & t : tests) { - bool test_skipped = false; - if ((t.second.type == tt_online && offline) || (t.second.type == tt_extended && !extended)) { + std::cout << std::left << std::setw(60) << t->description << " " << std::fixed << std::setw(6); + if (t->status == ts_skipped || (t->flags & tf_online && offline) || (t->flags & tf_extended && !extended) || (t->flags & tf_coro && !coro)) { skipped++; - test_skipped = true; + std::cout << "\u001b[33mSKIPPED"; } else { - if (t.second.success == false || t.second.executed == false) { - failed++; - } else { + if (t->status == ts_success) { passed++; + std::cout << "\u001b[32mPASS"; + } else { + failed++; + std::cout << (t->status == ts_not_executed ? "\u001b[31mNOT EXECUTED" : "\u001b[31mFAIL"); } } - std::cout << std::left << std::setw(60) << t.second.description << " " << std::fixed << std::setw(6) << (test_skipped ? "\u001b[33mSKIPPED" : (t.second.executed && t.second.success ? "\u001b[32mPASS" : (!t.second.executed ? "\u001b[31mNOT EXECUTED" : "\u001b[31mFAIL"))) << std::setw(0) << "\u001b[0m\n"; + std::cout << std::setw(0) << "\u001b[0m\n"; } std::cout << "\u001b[37;1m\nExecution finished in " << std::fixed << std::setprecision(3) << get_time() << std::setprecision(0) << " seconds.\nFailed: " << failed << " Passed: " << passed << (skipped ? " Skipped: " : "") << (skipped ? std::to_string(skipped) : "") << " Percentage: " << std::setprecision(2) << ((float)(passed) / (float)(passed + failed) * 100.0f) << "%\u001b[0m\n"; return failed; @@ -300,25 +180,25 @@ std::string get_token() { void wait_for_tests() { uint16_t ticks = 0; while (ticks < TEST_TIMEOUT) { - size_t executed = 0; - for (auto & t : tests) { - if (t.second.executed == true) { - executed++; - } else if (!t.second.executed && ((offline && t.second.type == tt_online) || (!extended && t.second.type == tt_extended))) { - executed++; - t.second.executed = true; - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mSKIPPED\u001b[0m] " << t.second.description << "\n"; + size_t finished = 0; + for (auto t : tests) { + if (t->status != ts_started) { + finished++; + } else if (is_skipped(*t)) { + finished++; + t->status = ts_skipped; + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mSKIPPED\u001b[0m] " << t->description << "\n"; } } - if (executed == tests.size()) { + if (finished == tests.size()) { std::this_thread::sleep_for(std::chrono::seconds(10)); return; } std::this_thread::sleep_for(std::chrono::seconds(1)); ticks++; } - for (auto &t : tests) { - if (!t.second.executed) - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[31mTIMEOUT\u001b[0m] " << t.second.description << "\n"; + for (auto t : tests) { + if (t->status == ts_started) + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[31mTIMEOUT\u001b[0m] " << t->description << "\n"; } } From 2cbc430573dba99e8d43b5e612fbfa3eb72316dc Mon Sep 17 00:00:00 2001 From: Amber Ehrlich Date: Mon, 21 Aug 2023 12:45:25 -0400 Subject: [PATCH 15/21] docs(coro): update docs for PR #763 --- docpages/advanced_reference/coroutines.md | 25 ++++---- include/dpp/coro/async.h | 64 ++++++++++++++++++--- include/dpp/coro/coroutine.h | 57 +++++++++++++++++- include/dpp/coro/task.h | 70 ++++++++++++++++++++++- 4 files changed, 191 insertions(+), 25 deletions(-) diff --git a/docpages/advanced_reference/coroutines.md b/docpages/advanced_reference/coroutines.md index 6d21002a9d..eefdc681dc 100644 --- a/docpages/advanced_reference/coroutines.md +++ b/docpages/advanced_reference/coroutines.md @@ -1,6 +1,6 @@ \page coroutines Advanced commands with coroutines -\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_CORO must 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). +\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. Additionally, 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). ### What is a coroutine? @@ -19,7 +19,7 @@ int main() { /* Message handler to look for a command called !file */ /* Make note of passing the event by value, this is important (explained below) */ - bot.on_message_create.co_attach([](dpp::message_create_t event) -> dpp::task { + bot.on_message_create.co_attach([](dpp::message_create_t event) -> dpp::job { dpp::cluster *cluster = event.from->creator; if (event.msg.content == "!file") { @@ -45,22 +45,19 @@ int main() { ~~~~~~~~~~~~~~~ -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. +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::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` 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. +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::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. -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. +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. -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. - -\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. -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. +\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**. ### Several steps in one \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). -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. +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 : ~~~~~~~~~~{.cpp} #include @@ -70,7 +67,7 @@ int main() { bot.on_log(dpp::utility::cout_logger()); - bot.on_slashcommand.co_attach([](dpp::slashcommand_t event) -> dpp::task { + bot.on_slashcommand.co_attach([](dpp::slashcommand_t event) -> dpp::job { if (event.command.get_command_name() == "addemoji") { dpp::cluster *cluster = event.from->creator; // Retrieve parameter values @@ -133,9 +130,9 @@ int main() { \note This next example is fairly advanced and makes use of many of both C++ and D++'s advanced features. -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. +Earlier we mentioned two other types of coroutines provided by dpp : `dpp::coroutine` and `dpp::task`. They both take their return type as a template parameter, which may be void. Both `dpp::job` and `dpp::task` start on the constructor for asynchronous execution, however only the latter can be co_await-ed, this allows you to retrieve its return value. If a `dpp::task` is destroyed before it ends, it is cancelled and will stop when it is resumed from the next `co_await`. `dpp::coroutine` also has a return value and can be co_await-ed, however it only starts when co_await-ing, meaning it is executed synchronously. -Here is an example of a command making use of that to retrieve the avatar of a specified user, or if missing, the sender : +Here is an example of a command making use of `dpp::task` to retrieve the avatar of a specified user, or if missing, the sender : ~~~~~~~~~~{.cpp} #include @@ -145,7 +142,7 @@ int main() { bot.on_log(dpp::utility::cout_logger()); - bot.on_slashcommand.co_attach([](dpp::slashcommand_t event) -> dpp::task{ + bot.on_slashcommand.co_attach([](dpp::slashcommand_t event) -> dpp::job { if (event.command.get_command_name() == "avatar") { // Make a nested coroutine to fetch the guild member requested, that returns it as an optional constexpr auto resolve_member = [](const dpp::slashcommand_t &event) -> dpp::task> { diff --git a/include/dpp/coro/async.h b/include/dpp/coro/async.h index 7b4e3ac5da..2eb5f18e6b 100644 --- a/include/dpp/coro/async.h +++ b/include/dpp/coro/async.h @@ -103,8 +103,9 @@ struct async_callback_data { }; /** - * @brief Base class of dpp::async. This class should not be used directly by a user, use dpp::async instead. + * @brief Base class of dpp::async. * + * @warning This class should not be used directly by a user, use dpp::async instead. * @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::async so a user cannot call await_suspend and await_resume directly. */ template @@ -299,11 +300,8 @@ class async_base { async_base &operator=(async_base &&other) noexcept = default; /** - * @brief First function called by the standard library when the object is co-awaited. + * @brief Check whether or not co_await-ing this would suspend the caller, i.e. if we have the result or not * - * Returns whether we already have the result of the API call and don't need to suspend the caller. - * - * @remark Do not call this manually, use the co_await keyword instead. * @return bool Whether we already have the result of the API call or not */ bool await_ready() const noexcept { @@ -316,7 +314,7 @@ class async_base { * Checks again for the presence of the result, if absent, signals to suspend and keep track of the calling coroutine for the callback to resume. * * @remark Do not call this manually, use the co_await keyword instead. - * @param handle The handle to the coroutine co_await-ing and being suspended + * @param caller The handle to the coroutine co_await-ing and being suspended */ bool await_suspend(detail::std_coroutine::coroutine_handle<> caller) noexcept { auto sent = detail::async_state_t::sent; @@ -372,7 +370,10 @@ struct confirmation_callback_t; template class async : private detail::async_base { /** - * @brief Base class has friend access for CRTP downcast + * @brief Internal use only base class. It serves to prevent await_suspend and await_resume from being used directly. + * + * @warning For internal use only, do not use. + * @see operator co_await() */ friend class detail::async_base; @@ -406,6 +407,55 @@ class async : private detail::async_base { #endif explicit async(Fun &&fun, Args&&... args) : detail::async_base{std::forward(fun), std::forward(args)...} {} +#ifdef _DOXYGEN_ // :) + /** + * @brief Construct an empty async. Using `co_await` on an empty async is undefined behavior. + */ + async() noexcept; + + /** + * @brief Destructor. If any callback is pending it will be aborted. + */ + ~async(); + + /** + * @brief Copy constructor is disabled + */ + async(const async &); + + /** + * @brief Move constructor + * + * 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. + * + * @remark Using the moved-from async after this function is undefined behavior. + * @param other The async object to move the data from. + */ + async(async &&other) noexcept = default; + + /** + * @brief Copy assignment is disabled + */ + async &operator=(const async &) = delete; + + /** + * @brief Move assignment operator. + * + * 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. + * + * @remark Using the moved-from async after this function is undefined behavior. + * @param other The async object to move the data from + */ + async &operator=(async &&other) noexcept = default; + + /** + * @brief Check whether or not co_await-ing this would suspend the caller, i.e. if we have the result or not + * + * @return bool Whether we already have the result of the API call or not + */ + bool await_ready() const noexcept; +#endif + /** * @brief Suspend the caller until the request completes. * diff --git a/include/dpp/coro/coroutine.h b/include/dpp/coro/coroutine.h index 5cb20905f6..d16facb7ee 100644 --- a/include/dpp/coro/coroutine.h +++ b/include/dpp/coro/coroutine.h @@ -44,8 +44,9 @@ template using coroutine_handle = std_coroutine::coroutine_handle>; /** - * @brief Base class of dpp::coroutine. This class should not be used directly by a user, use dpp::coroutine instead. + * @brief Base class of dpp::coroutine. * + * @warn This class should not be used directly by a user, use dpp::coroutine instead. * @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::coroutine so a user cannot call await_suspend and await_resume directly. */ template @@ -181,7 +182,10 @@ class coroutine_base { template class coroutine : private detail::coroutine_base { /** - * @brief Base class has friend access for CRTP downcast + * @brief Internal use only base class containing common logic between coroutine and coroutine. It also serves to prevent await_suspend and await_resume from being used directly. + * + * @warning For internal use only, do not use. + * @see operator co_await() */ friend class detail::coroutine_base; @@ -207,9 +211,54 @@ class coroutine : private detail::coroutine_base { } public: +#ifdef _DOXYGEN_ // :)))) + /** + * @brief Default constructor, creates an empty coroutine. + */ + coroutine() = default; + + /** + * @brief Copy constructor is disabled + */ + coroutine(const coroutine &) = delete; + + /** + * @brief Move constructor, grabs another coroutine's handle + * + * @param other Coroutine to move the handle from + */ + coroutine(coroutine &&other) noexcept; + + /** + * @brief Destructor, destroys the handle. + */ + ~coroutine(); + + /** + * @brief Copy assignment is disabled + */ + coroutine &operator=(const coroutine &) = delete; + + /** + * @brief Move assignment, grabs another coroutine's handle + * + * @param other Coroutine to move the handle from + */ + coroutine &operator=(coroutine &&other) noexcept; + + /** + * @brief First function called by the standard library when the coroutine is co_await-ed. + * + * @remark Do not call this manually, use the co_await keyword instead. + * @throws invalid_operation_exception if the coroutine is empty or finished. + * @return bool Whether the coroutine is done + */ + bool await_ready() const; +#else using detail::coroutine_base::coroutine_base; // use coroutine_base's constructors using detail::coroutine_base::operator=; // use coroutine_base's assignment operators using detail::coroutine_base::await_ready; // expose await_ready as public +#endif /** * @brief Suspend the caller until the coroutine completes. @@ -242,6 +291,7 @@ class coroutine : private detail::coroutine_base { } }; +#ifndef _DOXYGEN_ // don't generate this on doxygen because `using` doesn't work and 2 copies of coroutine_base's docs is enough /** * @brief Base type for a coroutine, starts on co_await. * @@ -293,6 +343,7 @@ class coroutine : private detail::coroutine_base { return static_cast&&>(*this); } }; +#endif /* _DOXYGEN_ */ namespace detail { template @@ -493,10 +544,12 @@ namespace detail { } // namespace detail +#ifndef _DOXYGEN_ inline void coroutine::await_resume_impl() const { if (handle.promise().exception) std::rethrow_exception(handle.promise().exception); } +#endif /* _DOXYGEN_ */ } // namespace dpp diff --git a/include/dpp/coro/task.h b/include/dpp/coro/task.h index 14228ce8f4..811aa4677f 100644 --- a/include/dpp/coro/task.h +++ b/include/dpp/coro/task.h @@ -68,8 +68,9 @@ template using task_handle = detail::std_coroutine::coroutine_handle>; /** - * @brief Base class of dpp::task. This class should not be used directly by a user, use dpp::task instead. + * @brief Base class of dpp::task. * + * @warning This class should not be used directly by a user, use dpp::task instead. * @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::task so a user cannot call await_suspend and await_resume directly. */ template @@ -244,8 +245,9 @@ requires (!std::is_reference_v) #endif class task : private detail::task_base { /** - * @brief Private base class containing common logic between task and task. It also serves to prevent await_suspend and await_resume from being used directly. + * @brief Internal use only base class containing common logic between task and task. It also serves to prevent await_suspend and await_resume from being used directly. * + * @warning For internal use only, do not use. * @see operator co_await() */ friend class detail::task_base; @@ -290,11 +292,71 @@ class task : private detail::task_base { } public: +#ifdef _DOXYGEN_ // :) + /** + * @brief Default constructor, creates a task not bound to a coroutine. + */ + task() = default; + + /** + * @brief Copy constructor is disabled + */ + task(const task &) = delete; + + /** + * @brief Move constructor, grabs another task's coroutine handle + * + * @param other Task to move the handle from + */ + task(task &&other) noexcept; + + /** + * @brief Destructor. + * + * Destroys the handle. + * @warning The coroutine must be finished before this is called, otherwise it runs the risk of being resumed after it is destroyed, resuming in use-after-free undefined behavior. + */ + ~task(); + + /** + * @brief Copy assignment is disabled + */ + task &operator=(const task &) = delete; + + /** + * @brief Move assignment, grabs another task's coroutine handle + * + * @param other Task to move the handle from + */ + task &operator=(task &&other) noexcept; + + /** + * @brief Function to check if the task has finished its execution entirely + * + * @return bool Whether the task is finished. + */ + [[nodiscard]] bool done() const noexcept; + + /** + * @brief Cancel the task, it will stop the next time it uses co_await. On co_await-ing this task, throws dpp::task_cancelled_exception. + */ + dpp::task& cancel() & noexcept; + + /** + * @brief Check whether or not a call to co_await will suspend the caller. + * + * This function is called by the standard library as a first step when using co_await. If it returns true then the caller is not suspended. + * @throws logic_exception if the task is empty. + * @return bool Whether not to suspend the caller or not + */ + bool await_ready() const; +#else using detail::task_base::task_base; // use task_base's constructors using detail::task_base::operator=; // use task_base's assignment operators using detail::task_base::done; // expose done() as public using detail::task_base::cancel; // expose cancel() as public using detail::task_base::await_ready; // expose await_ready as public +#endif /** * @brief Suspend the current coroutine until the task completes. @@ -327,6 +389,7 @@ class task : private detail::task_base { } }; +#ifndef _DOXYGEN_ // don't generate this on doxygen because `using` doesn't work and 2 copies of coroutine_base's docs is enough /** * @brief A coroutine task. It starts immediately on construction and can be co_await-ed, making it perfect for parallel coroutines returning a value. * @@ -386,6 +449,7 @@ class task : private detail::task_base { return static_cast&&>(*this); } }; +#endif /* _DOXYGEN_ */ namespace detail { /** @@ -622,10 +686,12 @@ std_coroutine::coroutine_handle<> detail::task_chain_final_awaiter::await_sus } // namespace detail +#ifndef _DOXYGEN_ inline void task::await_resume_impl() const { if (handle.promise().exception) std::rethrow_exception(handle.promise().exception); } +#endif /* _DOXYGEN_ */ } // namespace dpp From 8985170f2540195b50c8d5b0c228b58e059150ee Mon Sep 17 00:00:00 2001 From: Brain Date: Tue, 22 Aug 2023 02:33:46 +0100 Subject: [PATCH 16/21] ci: force reupload with clobber, and sign a copy of the source archives (#792) --- sign.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sign.sh b/sign.sh index 5bd6c0c93d..4545245175 100755 --- a/sign.sh +++ b/sign.sh @@ -2,8 +2,13 @@ rm -rf build/sign mkdir -p build/sign cd build/sign +gh release download "$1" -A tar.gz +sleep 2 +gh release download "$1" -A zip +sleep 2 gh release download "$1" +sleep 2 rm -fv *.asc find . -type f -exec gpg --armor --detach-sign {} \; -gh release upload "$1" *.asc +gh release upload "$1" ./* --clobber From f92adb492bf20d7910f02d6250669140032e2f68 Mon Sep 17 00:00:00 2001 From: Miuna <809711+Mishura4@users.noreply.github.com> Date: Tue, 22 Aug 2023 10:54:44 -0400 Subject: [PATCH 17/21] feat(build): add off-by-default DPP_USE_PCH option in CMake (#793) --- CMakeLists.txt | 1 + library/CMakeLists.txt | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index aabbc42a25..c1798137ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ option(DPP_BUILD_TEST "Build the test program" ON) option(DPP_NO_VCPKG "No VCPKG" OFF) option(DPP_CORO "Experimental support for C++20 coroutines" OFF) option(DPP_USE_EXTERNAL_JSON "Use an external installation of nlohmann::json" OFF) +option(DPP_USE_PCH "Use precompiled headers to speed up compilation" OFF) include(CheckCXXSymbolExists) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 4a8624cab9..310ce0cb9a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -230,6 +230,15 @@ foreach (fullmodname ${subdirlist}) $ ) + if (DPP_USE_PCH) + target_precompile_headers(${modname} PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../include/dpp/cluster.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../include/dpp/json.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../include/dpp/utility.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../include/dpp/restresults.h" + ) + endif() + if (WIN32 AND NOT MINGW) if (NOT WINDOWS_32_BIT) target_link_libraries(${modname} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/libssl.lib" From af3247acb842ac08d4a113f698847c8eee2a23e9 Mon Sep 17 00:00:00 2001 From: Brain Date: Tue, 22 Aug 2023 16:47:05 +0100 Subject: [PATCH 18/21] ci: we have to silence error reporting here or we get stderr intermixed into our changelog. this is just extra busywork during release that we dont want (#795) --- makerelease.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makerelease.sh b/makerelease.sh index 46311674f0..18092f9986 100755 --- a/makerelease.sh +++ b/makerelease.sh @@ -88,7 +88,7 @@ rm -rf "libdpp-$NEWVER-win32" cd .. echo "Create release..." -gh release create "v$NEWVER" --draft --title "v$NEWVER release" --notes "$(/usr/bin/php ../buildtools/changelog.php)" ./assets/*.zip ./assets/*.deb ./assets/*.rpm +gh release create "v$NEWVER" --draft --title "v$NEWVER release" --notes "$(/usr/bin/php -d error_reporting=off ../buildtools/changelog.php)" ./assets/*.zip ./assets/*.deb ./assets/*.rpm gh release create -R brainboxdotcc/windows-bot-template "v$NEWVER" --draft --title "v$NEWVER release" --notes "This release packages DPP v$NEWVER into the windows bot template" echo "Cleaning up..." cd .. From e1e3ea35346ced0374b7a56f27ff9be36f22006e Mon Sep 17 00:00:00 2001 From: wizard7377 <76231370+wizard7377@users.noreply.github.com> Date: Wed, 23 Aug 2023 04:22:38 -0400 Subject: [PATCH 19/21] docs: fixed several small things in docs (#797) --- docpages/01_frequently_asked_questions.md | 6 +++--- .../automating-with-jenkins.md | 4 ++-- .../clusters_shards_and_guilds.md | 4 ++-- .../advanced_reference/coding_style_standards.md | 2 +- docpages/advanced_reference/coroutines.md | 16 ++++++++-------- docpages/advanced_reference/governance.md | 2 +- .../advanced_reference/lambdas_and_locals.md | 8 ++++---- docpages/building/freebsd.md | 4 ++-- docpages/building/linux.md | 4 ++-- docpages/building/osx.md | 2 +- docpages/building/windows.md | 6 +++--- .../commandhandler.md | 2 +- .../interactions_and_components/components.md | 2 +- .../interactions_and_components/context_menus.md | 6 +++--- .../interactions_and_components/slashcommands.md | 6 +++--- .../upload_parameter.md | 4 ++-- docpages/example_programs/misc/eval.md | 8 ++++---- .../example_programs/music_and_audio/oggopus.md | 4 ++-- .../example_programs/the_basics/attachments.md | 4 ++-- .../the_basics/checking-member-permissions.md | 4 ++-- docpages/example_programs/the_basics/embeds.md | 2 +- docpages/example_programs/the_basics/firstbot.md | 4 ++-- docpages/install/install-linux-deb.md | 4 ++-- docpages/install/install-linux-rpm.md | 4 ++-- docpages/install/install-vcpkg.md | 2 +- docpages/install/install-windows-vs-zip.md | 2 +- docpages/make_a_bot/meson.md | 8 ++++---- docpages/make_a_bot/replit.md | 10 +++++----- docpages/make_a_bot/windows_vs.md | 4 ++-- 29 files changed, 69 insertions(+), 69 deletions(-) diff --git a/docpages/01_frequently_asked_questions.md b/docpages/01_frequently_asked_questions.md index 2d27c8ef6f..f8eb6ccbba 100644 --- a/docpages/01_frequently_asked_questions.md +++ b/docpages/01_frequently_asked_questions.md @@ -11,10 +11,10 @@ In production on TriviaBot, the bot takes approximately 2gb of ram to run 18 sep For a very small bot, you can get the memory usage as low as **6 megabytes** on a Raspberry Pi. ## How do I use this library in Windows? -The easiest way is to use our [template project](https://github.com/brainboxdotcc/windows-bot-template). If you are unable to do this, download the precompiled latest release from our GitHub releases, and take the dlls, .lib file, and header files (`bin`, `lib` and `include` directories), placing them in a easily accessible place on your computer. Go into Visual Studio project settings in a new project, and point the project directories (notably the library directories and and include directories) at the correct locations. Add the `include` folder you extracted to your include directories, and add `dpp.lib` to your library directories. Ensure the project is set to C++17 standard in the settings. You should then be able to compile example programs within that project. When you run the program you have compiled you must ensure that all the dll files from the `bin` directory exist in the same directory as your executable. +The easiest way is to use our [template project](https://github.com/brainboxdotcc/windows-bot-template). If you are unable to do this, download the precompiled latest release from our GitHub releases, and take the dlls, `.lib` file, and header files (`bin`, `lib` and `include` directories), placing them in a easily accessible place on your computer. Go into Visual Studio project settings in a new project, and point the project directories (notably the library directories and and include directories) at the correct locations. Add the `include` folder you extracted to your include directories, and add `dpp.lib` to your library directories. Ensure the project is set to C++17 standard in the settings. You should then be able to compile example programs within that project. When you run the program you have compiled you must ensure that all the dll files from the `bin` directory exist in the same directory as your executable. ## Does this library support Visual Studio 2022? -Yes! The master branch comes with pre-built binaries for Visual Studio 2022 and our windows bot template has a [vs2022 branch](https://github.com/brainboxdotcc/windows-bot-template/tree/vs2022) which you can clone to get Visual Studio 2022 specific code. For the time being we support both Visual Studio 2019 and 2022. At some point in the future only 2022 may be supported as 2019 becomes outdated. +Yes! The master branch comes with pre-built binaries for Visual Studio 2022 and our Windows bot template has a [vs2022 branch](https://github.com/brainboxdotcc/windows-bot-template/tree/vs2022) which you can clone to get Visual Studio 2022 specific code. For the time being we support both Visual Studio 2019 and 2022. At some point in the future only 2022 may be supported as 2019 becomes outdated. ## How much of the library is completed? All REST calls (outbound commands) are completed including all currently available interactions, and all Discord events are available. The library also has voice support. @@ -41,7 +41,7 @@ NO! Definitely not! We have tried to keep things as simple as possible. We only DPP is short for *D Plus Plus* (D++), a play on the Discord and C++ names. You'll see the library referred to as `dpp` within source code as `d++` is not a valid symbol so we couldn't exactly use that as our namespace name. ## Is D++ a single header library? -No, D++ is a classically designed library which installs itself to your library directory/system directory as a shared object or dll. You must link to its .lib file and include its header files to make use of it. We have no plans for a single-header build. +No, D++ is a classically designed library which installs itself to your library directory/system directory as a shared object or dll. You must link to its `.lib` file and include its header files to make use of it. We have no plans for a single-header build. ## Does this library support slash commands/interactions? Yes! This library supports slash commands and interactions. For more information please see \ref slashcommands "Using Slash Commands and Interactions". diff --git a/docpages/advanced_reference/automating-with-jenkins.md b/docpages/advanced_reference/automating-with-jenkins.md index 5c98f4ddc9..05bc078bac 100644 --- a/docpages/advanced_reference/automating-with-jenkins.md +++ b/docpages/advanced_reference/automating-with-jenkins.md @@ -1,10 +1,10 @@ \page automating-with-jenkins Automating your bot with Jenkins -\note This page does NOT go into explaining how to install Jenkins, nor how to initally setup Jenkins. This is a tutorial for the CMake version with Linux (more specifically Ubuntu 22.04 LTS). If you don't know how to use CMake or you don't use CMake for your bot (and would like to) then please visit [Building a Discord Bot using CMake/Unix](/buildcmake.html). If you wish to automate this tutorial from GitHub pushes then you can simply download the GitHub plugin for Jenkins, set that up and this tutorial will still work as this tutorial will only build what it can see! +\note This page does NOT go into explaining how to install Jenkins, nor how to initially setup Jenkins. This is a tutorial for the CMake version with Linux (more specifically Ubuntu 22.04 LTS). If you don't know how to use CMake or you don't use CMake for your bot (and would like to) then please visit [Building a Discord Bot using CMake/Unix](/buildcmake.html). If you wish to automate this tutorial from GitHub pushes then you can simply download the GitHub plugin for Jenkins, set that up and this tutorial will still work as this tutorial will only build what it can see! ### Getting started -First of all, you'll want to create your project. For this, we'll use a Freestyle project as we're just going to be called some bash commands to tell CMake to build. We'll be calling this "DiscordBot" but you can name is whatever you want (I would advise against non-ascii characters). +First of all, you'll want to create your project. For this, we'll use a Freestyle project as we're just going to be calling some bash commands to tell CMake to build. We'll be calling this "DiscordBot" but you can name is whatever you want (I would advise against non-ascii characters). \image html jenkinsproject.png diff --git a/docpages/advanced_reference/clusters_shards_and_guilds.md b/docpages/advanced_reference/clusters_shards_and_guilds.md index fd9a24192b..13dadc5747 100644 --- a/docpages/advanced_reference/clusters_shards_and_guilds.md +++ b/docpages/advanced_reference/clusters_shards_and_guilds.md @@ -101,7 +101,7 @@ int main() ## Shards -A cluster contains zero or more shards. Each shard maintains a persistent websocket connection to Discord via a websocket, which receives all events the bot is made aware of, e.g. messages, channel edits, etc. Requests to the API on the other hand go out to Discord as separate HTTP requests. +A cluster contains zero or more shards. Each shard maintains a persistent connection to Discord via a websocket, which receives all events the bot is made aware of, e.g. messages, channel edits, etc. Requests to the API on the other hand go out to Discord as separate HTTP requests. Small bots will require only one shard and this is the default when you instantiate a cluster. The library will automatically determine and create the correct number of shards needed, if you do not configure it by hand. If you do want to specify a number of shards, you can specify this when creating a cluster: @@ -175,4 +175,4 @@ Discord restricts how many shards you can connect to at any one time to one per ## Guilds -Guilds are what servers are known as to the Discord API. There can be up to **2500** of these per shard. Once you reach 2500 guilds on your bot, Discord force your bot to shard, the D++ library will automatically create additional shards to accomodate if not explicitly configured with a larger number. Discord *does not restrict sharding* to bots on 2500 guilds or above. You can shard at any size of bot, although it would be a waste of resources to do so unless it is required. +Guilds are what servers are known as to the Discord API. There can be up to **2500** of these per shard. Once you reach 2500 guilds on your bot, Discord force your bot to shard, the D++ library will automatically create additional shards to accommodate if not explicitly configured with a larger number. Discord *does not restrict sharding* to bots on 2500 guilds or above. You can shard at any size of bot, although it would be a waste of resources to do so unless it is required. diff --git a/docpages/advanced_reference/coding_style_standards.md b/docpages/advanced_reference/coding_style_standards.md index 25305d301b..f74ad16c99 100644 --- a/docpages/advanced_reference/coding_style_standards.md +++ b/docpages/advanced_reference/coding_style_standards.md @@ -27,7 +27,7 @@ void foo() { } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This applies to functions, `while` statements, `if` statments, lambdas, nearly anything that uses curly braces with statements! +This applies to functions, `while` statements, `if` statements, lambdas, nearly anything that uses curly braces with statements! ### Lists diff --git a/docpages/advanced_reference/coroutines.md b/docpages/advanced_reference/coroutines.md index eefdc681dc..0a1b2e50ef 100644 --- a/docpages/advanced_reference/coroutines.md +++ b/docpages/advanced_reference/coroutines.md @@ -4,9 +4,9 @@ ### What is a coroutine? -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. +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](/attach-file.html), but this time with a coroutine : +Let's revisit [attaching a downloaded file](/attach-file.html), but this time with a coroutine: ~~~~~~~~~~~~~~~{.cpp} @@ -47,17 +47,17 @@ int main() { 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::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. +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::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. -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. +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. -\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**. +\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 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. 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**. ### Several steps in one \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). -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 : +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: ~~~~~~~~~~{.cpp} #include @@ -130,9 +130,9 @@ int main() { \note This next example is fairly advanced and makes use of many of both C++ and D++'s advanced features. -Earlier we mentioned two other types of coroutines provided by dpp : `dpp::coroutine` and `dpp::task`. They both take their return type as a template parameter, which may be void. Both `dpp::job` and `dpp::task` start on the constructor for asynchronous execution, however only the latter can be co_await-ed, this allows you to retrieve its return value. If a `dpp::task` is destroyed before it ends, it is cancelled and will stop when it is resumed from the next `co_await`. `dpp::coroutine` also has a return value and can be co_await-ed, however it only starts when co_await-ing, meaning it is executed synchronously. +Earlier we mentioned two other types of coroutines provided by dpp: dpp::coroutine and dpp::task. They both take their return type as a template parameter, which may be void. Both dpp::job and dpp::task start on the constructor for asynchronous execution, however only the latter can be `co_await`-ed, this allows you to retrieve its return value. If a dpp::task is destroyed before it ends, it is cancelled and will stop when it is resumed from the next `co_await`. dpp::coroutine also has a return value and can be `co_await`-ed, however it only starts when `co_await`-ing, meaning it is executed synchronously. -Here is an example of a command making use of `dpp::task` to retrieve the avatar of a specified user, or if missing, the sender : +Here is an example of a command making use of dpp::task to retrieve the avatar of a specified user, or if missing, the sender: ~~~~~~~~~~{.cpp} #include diff --git a/docpages/advanced_reference/governance.md b/docpages/advanced_reference/governance.md index 2d5138636a..8955f2eaaa 100644 --- a/docpages/advanced_reference/governance.md +++ b/docpages/advanced_reference/governance.md @@ -16,4 +16,4 @@ For most decisions, these are discussed in our `#library-development` channel on ## Contingency -*In the case of any unforseen disaster such as death of the project leader, control over domain (the only part of the project which has a direct cost attached) would pass to his next of kin who would arrange for transfer to a pre-arranged trusted third party who would adminisrate the domain going forward. Everything else relating to D++ is hosted on GitHub and would continue as normal.* \ No newline at end of file +*In the case of any unforeseen disaster such as death of the project leader, control over domain (the only part of the project which has a direct cost attached) would pass to his next of kin who would arrange for transfer to a pre-arranged trusted third party who would administrate the domain going forward. Everything else relating to D++ is hosted on GitHub and would continue as normal.* \ No newline at end of file diff --git a/docpages/advanced_reference/lambdas_and_locals.md b/docpages/advanced_reference/lambdas_and_locals.md index 0179b190d4..20fba2b68c 100644 --- a/docpages/advanced_reference/lambdas_and_locals.md +++ b/docpages/advanced_reference/lambdas_and_locals.md @@ -2,11 +2,11 @@ If you are reading this page, you have likely been sent here by someone helping you diagnose why your bot is crashing or why seemingly invalid values are being passed into lambdas within your program that uses D++. -It is important to remember that when you put a lambda callback onto a function in D++, that this lambda will execute at some point in the **future**. As with all things in the future and as 80s Sci Fi movies will tell you, when you reach the future things may well have changed! +It is important to remember that when you put a lambda callback onto a function in D++, that this lambda will execute at some point in the **future**. As with all things in the future and as 80s Sci-Fi movies will tell you, when you reach the future things may well have changed! \image html delorean-time-travel.gif -To explain this situation and how it causes issues i'd like you to imagine the age old magic trick, where a magician sets a fine table full of cutlery, pots, pans and wine. He indicates to the audience that this is authentic, then with a whip of his wrist, he whips the tablecloth away, leaving the cutlery and other tableware in place (if he is any good as a magician!) +To explain this situation and how it causes issues I'd like you to imagine the age old magic trick, where a magician sets a fine table full of cutlery, pots, pans and wine. He indicates to the audience that this is authentic, then with a whip of his wrist, he whips the tablecloth away, leaving the cutlery and other tableware in place (if he is any good as a magician!) Now imagine the following code scenario. We will describe this code scenario as the magic trick above, in the steps below: @@ -31,7 +31,7 @@ In this scenario, the outer event, `on_message_create` is your tablecloth. The l * Best case scenario: you access invalid RAM no longer owned by your program by trying to write to `myvar`, and [your bot outright crashes horribly](https://www.youtube.com/watch?v=sm8qb2kP-fQ)! * Worse case scenario: you silently corrupt ram and end up spending days trying to track down a bug that subtly breaks your bot... -The situation i am trying to describe here is one of object and variable ownership. When you call a lambda, **always assume that every non global reference outside of that lambda will be invalid when the lambda is called**! For any non-global variable always take a **copy** of the variable (not reference, or pointer). Global variables or those declared directly in `main()` are safe to pass as references. +The situation I am trying to describe here is one of object and variable ownership. When you call a lambda, **always assume that every non-global reference outside of that lambda will be invalid when the lambda is called**! For any non-global variable always take a **copy** of the variable (not reference, or pointer). Global variables or those declared directly in `main()` are safe to pass as references. For example, if we were to fix the broken code above, we could rewrite it like this: @@ -45,7 +45,7 @@ bot.on_message_create([&bot](const dpp::message_create_t & event) { }); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Note however that when you set myvar within the inner lambda, this does **not effect** the value of the var outside it. Lambdas should be considered self-contained silos, and as they execute in other threads should not be relied upon to set anything that exists **outside of that lambda**. +Note, however that when you set `myvar` within the inner lambda, this does **not effect** the value of the var outside it. Lambdas should be considered self-contained silos, and as they execute in other threads should not be relied upon to set anything that exists **outside of that lambda**. \warning Always avoid just using `[&]` in a lambda to access all in the scope above. It is unlikely that half of this scope will still even be valid by the time you get a look at it! diff --git a/docpages/building/freebsd.md b/docpages/building/freebsd.md index 0d64b263c1..745346fab5 100644 --- a/docpages/building/freebsd.md +++ b/docpages/building/freebsd.md @@ -15,7 +15,7 @@ For voice support, additional dependencies are required cmake -B ./build cmake --build ./build -j8 -Replace the number after -j with a number suitable for your setup, usually the same as the number of cores on your machine. `cmake` will fetch any dependencies that are required for you and ensure they are compiled alongside the library. +Replace the number after `-j` with a number suitable for your setup, usually the same as the number of cores on your machine. `cmake` will fetch any dependencies that are required for you and ensure they are compiled alongside the library. ## 4. Install globally @@ -29,7 +29,7 @@ If you want to install the library, its dependencies and header files to a diffe Then once the build is complete, run `make install` to install to the location you specified. -## 7. Using the library +## 6. Using the library Once installed, you can make use of the library in standalone programs simply by including it and linking to it: diff --git a/docpages/building/linux.md b/docpages/building/linux.md index ad0d82775f..043823a042 100644 --- a/docpages/building/linux.md +++ b/docpages/building/linux.md @@ -7,7 +7,7 @@ cmake -B ./build cmake --build ./build -j8 -Replace the number after -j with a number suitable for your setup, usually the same as the number of cores on your machine. `cmake` will fetch any dependencies that are required for you and ensure they are compiled alongside the library. +Replace the number after `-j` with a number suitable for your setup, usually the same as the number of cores on your machine. `cmake` will fetch any dependencies that are required for you and ensure they are compiled alongside the library. ## 2. Install to /usr/local/include and /usr/local/lib @@ -23,7 +23,7 @@ Then once the build is complete, run `make install` to install to the location y ## 4. Using the library -Once installed to the /usr/local directory, you can make use of the library in standalone programs simply by including it and linking to it: +Once installed to the `/usr/local` directory, you can make use of the library in standalone programs simply by including it and linking to it: g++ -std=c++17 mydppbot.cpp -o dppbot -ldpp diff --git a/docpages/building/osx.md b/docpages/building/osx.md index a835ca0a21..45b855373e 100644 --- a/docpages/building/osx.md +++ b/docpages/building/osx.md @@ -20,7 +20,7 @@ For voice support, additional dependencies are required: cmake -B ./build cmake --build ./build -j8 -Replace the number after -j with a number suitable for your setup, usually the same as the number of cores on your machine. `cmake` will fetch any dependencies that are required for you and ensure they are compiled alongside the library. +Replace the number after `-j` with a number suitable for your setup, usually the same as the number of cores on your machine. `cmake` will fetch any dependencies that are required for you and ensure they are compiled alongside the library. ## 4. Install globally diff --git a/docpages/building/windows.md b/docpages/building/windows.md index f1aa6e0d25..20643eff4c 100644 --- a/docpages/building/windows.md +++ b/docpages/building/windows.md @@ -1,6 +1,6 @@ \page buildwindows Building on Windows -To build on windows follow these steps *exactly*. The build process depends on specific libraries being installed on your system in specific locations. +To build on Windows follow these steps *exactly*. The build process depends on specific libraries being installed on your system in specific locations. ## Wait a minute! Read this first! @@ -20,10 +20,10 @@ To build on windows follow these steps *exactly*. The build process depends on s ## Troubleshooting -* If you do not have an option to open the CMakeLists.txt, ensure that you have installed the C++ development portions of visual studio (not just web development portions) with at least the default options. +* If you do not have an option to open the CMakeLists.txt, ensure that you have installed the C++ development portions of Visual Studio (not just web development portions) with at least the default options. * If the project does not build, please ask for help on the [official discord server](https://discord.gg/dpp). ## After compiling -After compilation you can directly reference the compiled project in your own CMakeLists.txt as a library or use the lib/dll/headers as you wish. Note that `openssl` and `zlib` will also be an indirect dependency of your program (as `DLL` files) and should be copied alongside `dpp.dll`. +After compilation you can directly reference the compiled project in your own CMakeLists.txt as a library or use the `lib/dll/headers` as you wish. Note that `openssl` and `zlib` will also be an indirect dependency of your program (as `DLL` files) and should be copied alongside `dpp.dll`. diff --git a/docpages/example_programs/interactions_and_components/commandhandler.md b/docpages/example_programs/interactions_and_components/commandhandler.md index b1572c822c..d2a071fbcf 100644 --- a/docpages/example_programs/interactions_and_components/commandhandler.md +++ b/docpages/example_programs/interactions_and_components/commandhandler.md @@ -7,7 +7,7 @@ route both the /ping (global slash command) and .ping (prefixed channel message \note This example automatically hooks the dpp::cluster::on_message_create and dpp::cluster::on_slashcommand events. This can be overridden if needed to allow you to still make use of these functions for your own code, if you need to do this please see the constructor documentation for dpp::commandhandler. -Note that because the dpp::commandhandler::add_command method accepts a std::function as the command handler, you may point a command handler +Note that because the dpp::commandhandler::add_command method accepts a `std::function` as the command handler, you may point a command handler at a simple lambda (as shown in this example), a function pointer, or an instantiated class method of an object. This is extremely flexible and allows you to decide how and where commands should be routed, either to an object oriented system or to a lambda based system. diff --git a/docpages/example_programs/interactions_and_components/components.md b/docpages/example_programs/interactions_and_components/components.md index 125b3627f6..dbdba23af1 100644 --- a/docpages/example_programs/interactions_and_components/components.md +++ b/docpages/example_programs/interactions_and_components/components.md @@ -1,7 +1,7 @@ \page components Using button components Discord's newest features support sending buttons alongside messages, which when clicked by the user trigger an interaction which is routed by -D++ as an on_button_click event. To make use of this, use code as in this example. +D++ as an `on_button_click` event. To make use of this, use this code as in this example. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} #include diff --git a/docpages/example_programs/interactions_and_components/context_menus.md b/docpages/example_programs/interactions_and_components/context_menus.md index f33432e847..d810fcc0a0 100644 --- a/docpages/example_programs/interactions_and_components/context_menus.md +++ b/docpages/example_programs/interactions_and_components/context_menus.md @@ -1,10 +1,10 @@ \page context-menu Context Menus -Context menus are application commands that appear on the context menu (right click or tap) of users or messages to perform context-specific actions. They can be created using `dpp::slashcommand`. Once you create a context menu, try right-clicking either a user or message to see it in your server! +Context menus are application commands that appear on the context menu (right click or tap) of users or messages to perform context-specific actions. They can be created using dpp::slashcommand. Once you create a context menu, try right-clicking either a user or message to see it in your server! \image html context_menu_user_command.png -\note This example sets the command as the type dpp::ctxm_user which can only be used by right clicking on a user. To make it appear on a message, you'll want to switch the type to dpp::ctxm_message and listen for the on_message_context_menu (dpp::message_context_menu_t) event. +\note This example sets the command as the type dpp::ctxm_user which can only be used by right clicking on a user. To make it appear on a message, you'll want to switch the type to dpp::ctxm_message and listen for the `on_message_context_menu` (dpp::message_context_menu_t) event. The following example shows how to create and handle **user context menus** for message context menus, read the notice above. @@ -50,6 +50,6 @@ int main() } ~~~~~~~~~~ -It registers a guild command that can be called by right-click a user and click on the created menu. +It registers a guild command that can be called by right-clicking a user and clicking on the created menu. \image html context_menu_user_command_showcase.png \ No newline at end of file diff --git a/docpages/example_programs/interactions_and_components/slashcommands.md b/docpages/example_programs/interactions_and_components/slashcommands.md index 9ccfdc78c6..a6988fe4ad 100644 --- a/docpages/example_programs/interactions_and_components/slashcommands.md +++ b/docpages/example_programs/interactions_and_components/slashcommands.md @@ -4,11 +4,11 @@ Slash commands and interactions are a newer feature of Discord which allow bot's To add a slash command you should use the dpp::cluster::global_command_create method for global commands (available to all guilds) or dpp::cluster::guild_command_create to create a local command (available only to one guild). If you want to add many commands, it is advised to use the dpp::cluster::global_bulk_command_create method for global commands or the dpp::cluster::guild_bulk_command_create method for local commands. -\note dpp::cluster::global_bulk_command_create or dpp::cluster::guild_bulk_command_create will delete any previous commands that were registered. For example, if you call global_bulk_command_create twice with two different sets then the first set of commands will be created, then when the second set is called, the first set will be deleted, leaving only the second set. +\note dpp::cluster::global_bulk_command_create and dpp::cluster::guild_bulk_command_create will delete any previous commands that were registered. For example, if you call `global_bulk_command_create` twice with two different sets then the first set of commands will be created, then when the second set is called, the first set will be deleted, leaving only the second set. When a user issues these commands the reply will arrive via the `on_slashcommand` event which you can hook, and take action when you see your commands. It is possible to reply to an interaction by using either the dpp::interaction_create_t::reply method, or by manually instantiating an object of type dpp::interaction_response and attaching a dpp::message object to it. -dpp::interaction_create_t::reply has two overloaded versions of the method, one of which accepts simple std::string replies, for basic text-only messages (if your message is 'ephemeral' you must use this) and one which accepts a dpp::message for more advanced replies. Please note that at present, Discord only supports a small subset of message and embed features within an interaction response object. +dpp::interaction_create_t::reply has two overloaded versions of the method, one of which accepts simple `std::string` replies, for basic text-only messages (if your message is 'ephemeral' you must use this) and one which accepts a dpp::message for more advanced replies. Please note that at present, Discord only supports a small subset of message and embed features within an interaction response object. \note You can also use the unified command handler, which lets you combine channel based message commands and slash commands under the same lambda with the same code like they were one and the same. Note that after August of 2022 Discord will be discouraging bots from using commands that are prefixed messages via means of a privileged message intent. It is advised that you exclusively use slash commands, or the unified handler with only a prefix of "/" going forward for any new bots you create and look to migrating existing bots to this setup. @@ -209,4 +209,4 @@ int main() } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -\note For demonstration purposes, and small bots, this code is OK, but in the real world once your bot gets big, it's not recommended to create slash commands in the `on_ready` event even when it's inside `dpp::run_once` as, if you re-run your bot multiple times or start multiple clusters, you will quickly get rate-limited! You could, for example, add a commandline parameter to your bot (`argc`, `argv`) so that if you want the bot to register commands it must be launched with a specific command line argument. \ No newline at end of file +\note For demonstration purposes, and small bots, this code is OK, but in the real world once your bot gets big, it's not recommended to create slash commands in the `on_ready` event even when it's inside dpp::run_once as, if you re-run your bot multiple times or start multiple clusters, you will quickly get rate-limited! You could, for example, add a commandline parameter to your bot (`argc`, `argv`) so that if you want the bot to register commands it must be launched with a specific command line argument. \ No newline at end of file diff --git a/docpages/example_programs/interactions_and_components/upload_parameter.md b/docpages/example_programs/interactions_and_components/upload_parameter.md index 0ed39a8bd6..f971f20926 100644 --- a/docpages/example_programs/interactions_and_components/upload_parameter.md +++ b/docpages/example_programs/interactions_and_components/upload_parameter.md @@ -1,11 +1,11 @@ \page discord-application-command-file-upload Using file parameters for application commands (slash commands) The program below demonstrates how to use the 'file' type parameter to an application command (slash command). -You must first get the file_id via std::get, and then you can find the attachment details in the 'resolved' +You must first get the file_id via `std::get`, and then you can find the attachment details in the 'resolved' section, `event.command.resolved`. The file is uploaded to Discord's CDN so if you need it locally you should fetch the `.url` value, e.g. by using -something like `dpp::cluster::request()`. +something like dpp::cluster::request(). ~~~~~~~~~~~~~~~~{.cpp} #include diff --git a/docpages/example_programs/misc/eval.md b/docpages/example_programs/misc/eval.md index 369e7498cc..6f65bb300f 100644 --- a/docpages/example_programs/misc/eval.md +++ b/docpages/example_programs/misc/eval.md @@ -2,7 +2,7 @@ ### What is an eval command anyway? -Many times people will ask: "how do i make a command like 'eval' in C++". For the uninitiated, an `eval` command is a command often found in interpreted languages such as Javascript and Python, which allows the developer to pass in raw interpreter statements which are then executed within the context of the running program, without any sandboxing. Eval commands are plain **evil**, if not properly coded in. +Many times people will ask: "How do I make a command like 'eval' in C++?". For the uninitiated, an `eval` command is a command often found in interpreted languages such as Javascript and Python, which allows the developer to pass in raw interpreter statements which are then executed within the context of the running program, without any sandboxing. Eval commands are plain **evil**, if not properly coded in. Needless to say, this is very dangerous. If you are asking how to do this, and want to put this into your bot, we trust that you have a very good reason to do so and have considered alternatives before resorting to this. The code below is for educational purposes only and makes several assumptions: @@ -19,8 +19,8 @@ The evaluated code will run within its own thread, so can execute for as long as ### Implementation details This code operates by outputting your provided code to be evaluated into a simple boilerplate program which can be compiled to a -shared object library (.so file). This .so file is then compiled with g++, using the `-shared` and `-fPIC` flags. If the program can be successfully compiled, it is then loaded using `dlopen()`, and the symbol `so_exec()` searched for within it, and called. This `so_exec()` function will contain the body of the code given to the eval command. Once this has been called and it has returned, -the `dlclose()` function is called to unload the library, and finally any temporary files (such as the .so file and its corresponding .cpp file) are cleaned up. +shared object library (`.so`) file. This `.so` file is then compiled with `g++`, using the `-shared` and `-fPIC` flags. If the program can be successfully compiled, it is then loaded using `dlopen()`, and the symbol `so_exec()` searched for within it, and called. This `so_exec()` function will contain the body of the code given to the eval command. Once this has been called and it has returned, +the `dlclose()` function is called to unload the library, and finally any temporary files (such as the `.so` file and its corresponding `.cpp` file) are cleaned up. Docker is definitely recommended if you code on Windows/Mac OS, because docker desktop still uses a linux VM, so your code can easily use `.so` file and your code runs the same on your vps (if it also uses Linux distro) ### Source code @@ -29,7 +29,7 @@ Docker is definitely recommended if you code on Windows/Mac OS, because docker d #### eval.h -Remember that eval.h contains forward-declarations of any functions you want to expose to the eval command. It is included both by the bot itself, and by any shared object files compiled for evaluation. +Remember that `eval.h` contains forward-declarations of any functions you want to expose to the eval command. It is included both by the bot itself, and by any shared object files compiled for evaluation. ~~~~~~~~~~~~~~~~{.cpp} #pragma once diff --git a/docpages/example_programs/music_and_audio/oggopus.md b/docpages/example_programs/music_and_audio/oggopus.md index def7807bbf..991597b6c5 100644 --- a/docpages/example_programs/music_and_audio/oggopus.md +++ b/docpages/example_programs/music_and_audio/oggopus.md @@ -191,7 +191,7 @@ You can compile this example using the following command ## Using liboggz You can use `liboggz` to stream an Ogg Opus file to discord voice channel. -`liboggz` provides higher level abstraction and useful APIs. Some API `liboggz` provide includes seeking and timestamp interpretation. +`liboggz` provides higher level abstraction and useful APIs. Some features `liboggz` provides include: seeking and timestamp interpretation. Read more on the [documentation](https://www.xiph.org/oggz/doc/). ~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} @@ -314,6 +314,6 @@ int main(int argc, char const *argv[]) } ~~~~~~~~~~~~~~~~~~~~~~~~~ -You can compile this example using the following command +You can compile this example using the following command: c++ /path/to/source.cc -ldpp -loggz \ No newline at end of file diff --git a/docpages/example_programs/the_basics/attachments.md b/docpages/example_programs/the_basics/attachments.md index a8ed5ba4d2..a6a398a119 100644 --- a/docpages/example_programs/the_basics/attachments.md +++ b/docpages/example_programs/the_basics/attachments.md @@ -4,7 +4,7 @@ Attached files must be locally stored. To attach a file to a message, you can upload a local image. -D++ has this helper function to read a file: `dpp::utility::read_file`. +D++ has this helper function to read a file: dpp::utility::read_file. An example program: @@ -47,7 +47,7 @@ int main() { Attachments via an url aren't possible. But there's a workaround for. You can download the file and then attach it to the message. -To make requests, D++ also has a helper function: `dpp::cluster::request`. +To make requests, D++ also has a helper function: dpp::cluster::request. The following example program shows how to request a file and attach it to a message. diff --git a/docpages/example_programs/the_basics/checking-member-permissions.md b/docpages/example_programs/the_basics/checking-member-permissions.md index 02760b537f..b10bdab43e 100644 --- a/docpages/example_programs/the_basics/checking-member-permissions.md +++ b/docpages/example_programs/the_basics/checking-member-permissions.md @@ -64,7 +64,7 @@ bot.on_interaction_create([](const dpp::interaction_create_t& event) { The resolved set also contains the permissions of members from command parameters. -For example let's say you want to prohibit people from banning server admins with your ban command. +For example, let's say you want to prohibit people from banning server admins with your ban command. Get the user ID from the parameters and pass it to the `get_resolved_permission` method: @@ -91,4 +91,4 @@ bot.on_interaction_create([](const dpp::interaction_create_t& event) { return; } }); -``` \ No newline at end of file +``` diff --git a/docpages/example_programs/the_basics/embeds.md b/docpages/example_programs/the_basics/embeds.md index 051a83f408..aad11535a8 100644 --- a/docpages/example_programs/the_basics/embeds.md +++ b/docpages/example_programs/the_basics/embeds.md @@ -1,6 +1,6 @@ \page embed-message Sending Embeds -If you've been in a server and used a bot or even seen a message from a bot, you might have seen a special messages, often sent by these bots. These are called embeds! In this section, we will show how to create an embed and reply to a user, using our newly created embed! +If you've been in a server and used a bot or even seen a message from a bot, you might have seen a special message type, often sent by these bots. These are called embeds! In this section, we will show how to create an embed and reply to a user, using our newly created embed! ~~~~~~~~~~{.cpp} #include diff --git a/docpages/example_programs/the_basics/firstbot.md b/docpages/example_programs/the_basics/firstbot.md index 97e2dca5ad..d80ca5ebad 100644 --- a/docpages/example_programs/the_basics/firstbot.md +++ b/docpages/example_programs/the_basics/firstbot.md @@ -111,7 +111,7 @@ int main() { ### 3. Attach to an event To have a bot that does something, you should attach to some events. Let's start by attaching to the `on_ready` event (dpp::cluster::on_ready) which will notify your program when the bot is connected. In this event, we will register a slash -command called 'ping'. Note that we must wrap our registration of the command in a template called `dpp::run_once` which prevents it from being re-run +command called 'ping'. Note that we must wrap our registration of the command in a template called dpp::run_once which prevents it from being re-run every time your bot does a full reconnection (e.g. if the connection fails). ~~~~~~~~~~~~~~~~{.cpp} @@ -196,7 +196,7 @@ The `event.reply` function (dpp::slashcommand_t::reply) replies to a slash comma ### 6. Add code to start the bot! -To make the bot start, we must call the cluster::start method, e.g. in our program by using `bot.start(dpp::st_wait)`. +To make the bot start, we must call the dpp::cluster::start method, e.g. in our program by using `bot.start(dpp::st_wait)`. We also add a line to tell the library to output all its log information to the console, `bot.on_log(dpp::utility::cout_logger());` - if you wanted to do something more advanced, you can replace this parameter with a lambda just like all other events. diff --git a/docpages/install/install-linux-deb.md b/docpages/install/install-linux-deb.md index d0ddb79a81..80efdb092a 100644 --- a/docpages/install/install-linux-deb.md +++ b/docpages/install/install-linux-deb.md @@ -1,6 +1,6 @@ \page install-linux-deb Installing from a .deb file (Debian, Ubuntu, Derivatives) -To install D++ on a system from .deb using dpkg (as root): +To install D++ on a system from `.deb` using `dpkg` (as root): ``` apt install wget @@ -12,7 +12,7 @@ This will do the following three things: - Install `wget` - Use `wget` to download the latest release of D++ to `dpp.deb` -- Install `dpp.deb` to /usr +- Install `dpp.deb` to `/usr` You will now be able to use D++ by including its library on the command line: diff --git a/docpages/install/install-linux-rpm.md b/docpages/install/install-linux-rpm.md index e42e40e2a3..a5faea8350 100644 --- a/docpages/install/install-linux-rpm.md +++ b/docpages/install/install-linux-rpm.md @@ -1,6 +1,6 @@ \page install-linux-rpm Installing from a .rpm file (RedHat, CentOS and derivatives) -To install D++ on a system from .rpm using `yum` (as root): +To install D++ on a system from `.rpm` using `yum` (as root): ``` yum install wget @@ -12,7 +12,7 @@ This will do the following three things: - Install `wget` - Use `wget` to download the latest release of D++ to `dpp.rpm` -- Install `dpp.rpm` to /usr +- Install `dpp.rpm` to `/usr` You will now be able to use D++ by including its library on the command line: diff --git a/docpages/install/install-vcpkg.md b/docpages/install/install-vcpkg.md index 2faf33475f..f7f44629f1 100644 --- a/docpages/install/install-vcpkg.md +++ b/docpages/install/install-vcpkg.md @@ -12,7 +12,7 @@ To install D++ on a system from VCPKG: c:\vcpkg>vcpkg list dpp dpp:x64-windows 10.0.15 D++ Extremely Lightweight C++ Discord Library. ``` -- You may now use the library within a `cmake` based project by adding instructions such as these to your `CmakeLists.txt`: +- You may now use the library within a `CMake` based project by adding instructions such as these to your `CMakeLists.txt`: ```cmake find_package(dpp CONFIG REQUIRED) target_link_libraries(your_target_name PRIVATE dpp::dpp) diff --git a/docpages/install/install-windows-vs-zip.md b/docpages/install/install-windows-vs-zip.md index 241c786b37..3bf5a10885 100644 --- a/docpages/install/install-windows-vs-zip.md +++ b/docpages/install/install-windows-vs-zip.md @@ -23,7 +23,7 @@ To add D++ to a Visual Studio project, using **Visual Studio 2019** or **Visual \image html zip_vsproj_8.png 10. Double check at this point that all the directories are filled in correctly. They should look generally like the ones in the screenshot below: \image html zip_vsproj_9.png -11. Go to the general section in the same window now, and look for the drop down list laballed "C++ Language Standard". Make sure the selected option is **C++17 Standard (/std:c++17)** +11. Go to the general section in the same window now, and look for the drop down list labelled "C++ Language Standard". Make sure the selected option is **C++17 Standard (/std:c++17)** \image html zip_vsproj_10.png 12. Again within the same window, go to the input section, under the linker category, and add '**dpp.lib;**' to the start of the libraries to include, as shown below: \image html zip_vsproj_11.png diff --git a/docpages/make_a_bot/meson.md b/docpages/make_a_bot/meson.md index ff4a7d8525..db307bf7f4 100644 --- a/docpages/make_a_bot/meson.md +++ b/docpages/make_a_bot/meson.md @@ -20,22 +20,22 @@ run the command ## 3. Configuring your Meson project -add the following line after the project() line in your meson.build file. +add the following line after the `project()` line in your `meson.build` file. dpp = dependency('dpp') -add the following line in the executable section of your meson.build file. +add the following line in the executable section of your `meson.build` file. dependencies: [dpp] -change the cpp_std value in the project() to c++17 +change the `cpp_std` value in the `project()` to `c++17` your meson.build should look like this. ~~~~~~~~~~~~~~ project('discord-bot', 'cpp', version : '0.1', default_options : ['warning_level=3', - 'cpp_std=c++14']) + 'cpp_std=c++17']) dpp = dependency('dpp') diff --git a/docpages/make_a_bot/replit.md b/docpages/make_a_bot/replit.md index ae51a86b75..e3eae1d7d0 100644 --- a/docpages/make_a_bot/replit.md +++ b/docpages/make_a_bot/replit.md @@ -1,8 +1,8 @@ -\page building-a-cpp-discord-bot-in-repl Creating a Discord bot in Repl.it +\page building-a-cpp-discord-bot-in-repl Creating a Discord bot in Replit @note There is a premade repl, ready for use which was built using the steps above. If you wish to use this repl simply [visit this github repository](https://github.com/alanlichen/dpp-on-repl) and click the "Run on Replit" button. Then, follow the steps in the README file. -To build a D++ bot in a repl.it instance, follow these steps. These steps are slightly more convoluted than installing D++ into a standard container as we don't have access to root in the conventional way or write access to any files outside of our home directory in a repl. This guide sidesteps the issue by locally extracting a libdpp deb file installer, and referencing the local dependencies from the command-line. +To build a D++ bot in a Replit instance, follow these steps. These steps are slightly more convoluted than installing D++ into a standard container as we don't have access to root in the conventional way or write access to any files outside of our home directory in a repl. This guide sidesteps the issue by locally extracting a libdpp deb file installer, and referencing the local dependencies from the command-line. 1. Use wget, or the upload button, to get the precompiled x64 release into your repl as a file, e.g. `wget -O libdpp.deb https://dl.dpp.dev/latest` 2. Extract this deb file using `dpkg`: @@ -24,8 +24,8 @@ Now that your bot is running, you have to keep it online. Replit automatically p ``` python3 -m http.server ``` -2. Create an index.html file with anything inside it for the server to serve. -3. Go to [uptimerobot.com](https://uptimerobot.com/) and create an account if you dont have one. +2. Create an `index.html` file with anything inside it for the server to serve. +3. Go to [uptimerobot.com](https://uptimerobot.com/) and create an account if you don't have one. 4. After verifying your account, click "Add New Monitor". + For Monitor Type, select "HTTP(s)" + In Friendly Name, put the name of your bot @@ -37,6 +37,6 @@ Here is an example of a possible uptimerobot configuration: ## Troubleshooting -If the bot fails to start and instead you receive an error message about being banned from the Discord API, there is little to be done about this. These bans are temporary but because repl.it is a shared platform, you share an IP address with many thousands of bots, some abusive and some badly written. This will happen often and is outside of the control of yourself and us. However, you can try to migitate this by typing `kill 1` in the shell. This is not guaranteed to work, and you might need to try it a few times. If it still does not work, then we recommend instead you obtain some affordable non-free hosting instead. +If the bot fails to start and instead you receive an error message about being banned from the Discord API, there is little to be done about this. These bans are temporary but because Replit is a shared platform, you share an IP address with many thousands of bots, some abusive and some badly written. This will happen often and is outside of the control of yourself and us. However, you can try to migitate this by typing `kill 1` in the shell. This is not guaranteed to work, and you might need to try it a few times. If it still does not work, then we recommend instead you obtain some affordable non-free hosting instead. If your bot continues to fall asleep even though you have a server, we advise you to double check that no errors are happening, and if the server is being pinged. If that still does not work, we again recommend you to obtain some affordable non-free hosting. diff --git a/docpages/make_a_bot/windows_vs.md b/docpages/make_a_bot/windows_vs.md index 535c84774b..af228badda 100644 --- a/docpages/make_a_bot/windows_vs.md +++ b/docpages/make_a_bot/windows_vs.md @@ -3,8 +3,8 @@ To create a basic bot using **Visual Studio 2019** or **Visual Studio 2022**, follow the steps below to create a *working skeleton project you can build upon*. 1. Make sure you have Visual Studio 2019 or 2022. Community, Professional or Enterprise work fine. These instructions are not for Visual Studio Code. You can [download the correct version here](https://visualstudio.microsoft.com/downloads/). Note that older versions of Visual Studio will not work as they do not support enough of the C++17 standard. -2. Clone the [template project](https://github.com/brainboxdotcc/windows-bot-template/). Be sure to clone the entire project and not just copy and paste the cpp file. -3. Double click on the MyBot.sln file in the folder you just cloned +2. Clone the [template project](https://github.com/brainboxdotcc/windows-bot-template/). Be sure to clone the entire project and not just copy and paste the `.cpp` file. +3. Double click on the `MyBot.sln` file in the folder you just cloned \image html vsproj_1.png 4. Add your bot token (see \ref creating-a-bot-application) and guild ID to the example program \image html vsproj_2.png From 7faa78a56173f788940f175c67498ffb8a578de1 Mon Sep 17 00:00:00 2001 From: Jaskowicz1 <46899449+Jaskowicz1@users.noreply.github.com> Date: Wed, 23 Aug 2023 13:02:09 +0100 Subject: [PATCH 20/21] docs: improved Jenkins page. (#798) --- .../advanced_reference/automating-with-jenkins.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docpages/advanced_reference/automating-with-jenkins.md b/docpages/advanced_reference/automating-with-jenkins.md index 05bc078bac..a191ee3560 100644 --- a/docpages/advanced_reference/automating-with-jenkins.md +++ b/docpages/advanced_reference/automating-with-jenkins.md @@ -4,11 +4,11 @@ ### Getting started -First of all, you'll want to create your project. For this, we'll use a Freestyle project as we're just going to be calling some bash commands to tell CMake to build. We'll be calling this "DiscordBot" but you can name is whatever you want (I would advise against non-ascii characters). +First of all, you'll want to create your project. For this, we'll use a "Freestyle project" as we're just going to be calling some bash commands to tell CMake to build. We'll be calling this "DiscordBot" but you can name it whatever you want (I would advise against non-ascii characters). \image html jenkinsproject.png -From here, just hit `Ok` and you've created your Jenkins project, Well done! From here you can add a description, change the security policy (if your jenkins is public) and whatnot. +From here, just hit `Ok` and now you've created your Jenkins project, Well done! From here you can add a description, change the security policy (if your jenkins is public), really whatever your heart desires. ### Automating the building process. @@ -32,7 +32,9 @@ fi cmake --build build/ ~~~~~~~~~~ -This script will build your project for you and also setup CMake if you've deleted the build directory. You can change this to a build parameter if you want, meaning you can hit `Build with Parameters` and state what you'd like to do. +\note This script does not make use of multiple threads. If you know how to do this and you would like to use threads, then feel free to. However, I would be careful not to use all your threads as Jenkins may dislike this. + +This script will build your project for you and also setup CMake if you've deleted the build directory, allowing you to easily refresh CMake. You can change this to a build parameter if you want, meaning you can hit `Build with Parameters` and state what you'd like to do (This would require the script to be changed so only do that if you know what you're doing)! \image html shelljenkins.png @@ -50,9 +52,11 @@ Making sure you have your project files in the workspace directory (you can see Running the builds is the same as any other time, but we'll still cover it! However, we won't cover running it in background and whatnot, that part is completely down to you. -First, you need to get into the jenkins user. If you're like me and don't have the Jenkins user password, you can login with your normal login (that has sudo perms) and do `sudo su - jenkins`. Once logged in, you'll be in `/var/lib/jenkins/`. From here, you'll want to do `cd workspace/DiscordBot` (make sure to replace "DiscordBot" with your bot's name. Remember, you can tab-complete this) and then `cd build`. Now, you can simply do `./DiscordBot`! +First, you need to get into the jenkins user. If you're like me and don't have the Jenkins user password, you can login with your normal login (that has sudo perms) and do `sudo su - jenkins`. Once logged in, you'll be in `/var/lib/jenkins/`. From here, you'll want to do `cd workspace/DiscordBot/` (make sure to replace "DiscordBot" with your bot's name. Remember, you can tab-complete this) and then `cd build`. Now, you can simply do `./DiscordBot`! + +\warning If you are going to be running the bot at the same time as builds, I would heavily advise that you copy the bot (if it's not statically linked, then copy the entire build directory) to a different location. This is so you can pick and choose when the bot gets updated (and even means you can run experimental builds as opposed to stable builds) but also means you avoid any risk of the bot crashing during build (as jenkins will be overwriting your executable and libraries). -That's it! Enjoy your automated builds! +Once you're happy with everything, then you're good to go! Enjoy your automated builds! ### Possible permission issues From 2e6f6b87b7836e505b6d46ee51666ff3c238de49 Mon Sep 17 00:00:00 2001 From: Jaskowicz1 <46899449+Jaskowicz1@users.noreply.github.com> Date: Wed, 23 Aug 2023 13:11:09 +0100 Subject: [PATCH 21/21] docs: Added a new page for on_message_create and changed warnings about message content privilege. (#799) --- .../interactions_and_components.md | 5 ++- .../commandhandler.md | 9 +++-- .../detecting-messages.md | 36 ++++++++++++++++++ .../slashcommands.md | 2 - docpages/images/badwordfilter.png | Bin 0 -> 55935 bytes 5 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 docpages/example_programs/interactions_and_components/detecting-messages.md create mode 100644 docpages/images/badwordfilter.png diff --git a/docpages/example_programs/interactions_and_components.md b/docpages/example_programs/interactions_and_components.md index aed7d5441e..4bcc079de1 100644 --- a/docpages/example_programs/interactions_and_components.md +++ b/docpages/example_programs/interactions_and_components.md @@ -3,11 +3,12 @@ The example programs listed here demonstrate lots of things to do with interactions, application commands (slash commands) and message components. If you're looking to make your bot **modern and user friendly** these examples are what you need. * \subpage slashcommands "Using Slash Commands and Interactions" -* \subpage context-menu "Context Menus" * \subpage subcommands "Slash command sub-commands" * \subpage components "Using button components" +* \subpage components2 "Advanced button components" * \subpage components3 "Using select menu components" -* \subpage components2 "Advanced components" +* \subpage detecting-messages "Listening to messages" +* \subpage context-menu "Context Menus" * \subpage modal-dialog-interactions "Modal Dialogs" * \subpage commandhandler "Unified message/slash command handler" * \subpage application-command-autocomplete "Slash command auto completion" diff --git a/docpages/example_programs/interactions_and_components/commandhandler.md b/docpages/example_programs/interactions_and_components/commandhandler.md index d2a071fbcf..55b29da05f 100644 --- a/docpages/example_programs/interactions_and_components/commandhandler.md +++ b/docpages/example_programs/interactions_and_components/commandhandler.md @@ -5,24 +5,27 @@ prefixed channel messages) you should consider instantiating a dpp::commandhandl commands and their parameters to functions in your program. A simple example of using this object to route commands is shown below, and will route both the /ping (global slash command) and .ping (prefixed channel message command) to a lambda where a reply can be generated. -\note This example automatically hooks the dpp::cluster::on_message_create and dpp::cluster::on_slashcommand events. This can be overridden if needed to allow you to still make use of these functions for your own code, if you need to do this please see the constructor documentation for dpp::commandhandler. +\note This example automatically hooks the dpp::cluster::on_message_create and dpp::cluster::on_slashcommand events. This can be overridden if needed to allow you to still make use of these functions for your own code, if you need to do this please see the constructor documentation for dpp::commandhandler. Note that because the dpp::commandhandler::add_command method accepts a `std::function` as the command handler, you may point a command handler at a simple lambda (as shown in this example), a function pointer, or an instantiated class method of an object. This is extremely flexible and allows you to decide how and where commands should be routed, either to an object oriented system or to a lambda based system. +\warning As of August 30th, 2022, you are advised to only be using slash commands, not messages for commands. To prevent the command handler from handling commands with messages, you should only use the "/" prefix. If you wish to still use messages for commands, this tutorial will still cover it but, again, it is discouraged by Discord. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} #include int main() { - dpp::cluster bot("token"); + /* If your bot only uses the "/" prefix, you can remove the intents here. */ + dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); bot.on_log(dpp::utility::cout_logger()); /* Create command handler, and specify prefixes */ dpp::commandhandler command_handler(&bot); - /* Specifying a prefix of "/" tells the command handler it should also expect slash commands */ + /* Specifying a prefix of "/" tells the command handler it should also expect slash commands. Remove the .add_prefix(".") if you wish to only make it a slash command */ command_handler.add_prefix(".").add_prefix("/"); bot.on_ready([&command_handler](const dpp::ready_t &event) { diff --git a/docpages/example_programs/interactions_and_components/detecting-messages.md b/docpages/example_programs/interactions_and_components/detecting-messages.md new file mode 100644 index 0000000000..82e2c9ac5b --- /dev/null +++ b/docpages/example_programs/interactions_and_components/detecting-messages.md @@ -0,0 +1,36 @@ +\page detecting-messages Listening to messages + +Sometimes, you may want to listen out for a message, rather than a command. This could be used for many cases like a spam filter, a bot that would respond to movie quotes with gifs, or a chat bot! However, in this page, we'll be using this to create a moderation filter (detect bad words). + +\warning As of August 30th, 2022, Discord made Message Content a privileged intent. Whilst this means you can still use prefixed messages as commands, Discord does not encourage this and heavily suggests you use [slash commands](/slashcommands.html). If you wish to create commands, use [slash commands](/slashcommands.html), not messages. + +~~~~~~~~~~{.cpp} +#include + +int main() +{ + /* Create the bot, but with our intents so we can use messages. */ + dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); + + bot.on_log(dpp::utility::cout_logger()); + + /* The event is fired when the bot detects a message in any server and any channel it has access to. */ + bot.on_message_create([&bot](const dpp::message_create_t& event) { + + /* See if the message contains the phrase we want to check for. + * If there's at least a single match, we reply and say it's not allowed. + */ + if (event.msg.content.find("bad word") != std::string::npos) { + event.reply("That is not allowed here. Please, mind your language!", true); + } + }); + + bot.start(dpp::st_wait); + + return 0; +} +~~~~~~~~~~ + +If all went well, you should have something like this! + +\image html badwordfilter.png \ No newline at end of file diff --git a/docpages/example_programs/interactions_and_components/slashcommands.md b/docpages/example_programs/interactions_and_components/slashcommands.md index a6988fe4ad..57f3bac606 100644 --- a/docpages/example_programs/interactions_and_components/slashcommands.md +++ b/docpages/example_programs/interactions_and_components/slashcommands.md @@ -10,8 +10,6 @@ When a user issues these commands the reply will arrive via the `on_slashcommand dpp::interaction_create_t::reply has two overloaded versions of the method, one of which accepts simple `std::string` replies, for basic text-only messages (if your message is 'ephemeral' you must use this) and one which accepts a dpp::message for more advanced replies. Please note that at present, Discord only supports a small subset of message and embed features within an interaction response object. -\note You can also use the unified command handler, which lets you combine channel based message commands and slash commands under the same lambda with the same code like they were one and the same. Note that after August of 2022 Discord will be discouraging bots from using commands that are prefixed messages via means of a privileged message intent. It is advised that you exclusively use slash commands, or the unified handler with only a prefix of "/" going forward for any new bots you create and look to migrating existing bots to this setup. - This first example goes over creating a single command globally. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} diff --git a/docpages/images/badwordfilter.png b/docpages/images/badwordfilter.png new file mode 100644 index 0000000000000000000000000000000000000000..ec6d3b841ab74d1a7ef2b9e618e5994cb842ac41 GIT binary patch literal 55935 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 literal 0 HcmV?d00001