Skip to content

Commit

Permalink
refactor(coro): deprecate dpp::job handler & push dpp::task instead, …
Browse files Browse the repository at this point in the history
…and remove static_assert in dpp::job (#958)
  • Loading branch information
Mishura4 authored Oct 22, 2023
1 parent 4744531 commit e72ac76
Show file tree
Hide file tree
Showing 15 changed files with 223 additions and 73 deletions.
4 changes: 2 additions & 2 deletions docpages/example_code/coro_awaiting_events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ int main() {

bot.on_log(dpp::utility::cout_logger());

bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job {
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
if (event.command.get_command_name() == "test") {
// Make a message and add a button with its custom ID set to the command interaction's ID so we can identify it
dpp::message m{"Test"};
Expand All @@ -32,7 +32,7 @@ int main() {
}
});

bot.on_ready([&bot](const dpp::ready_t & event) {
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
dpp::slashcommand command{"test", "Test awaiting for an event", bot.me.id};

Expand Down
4 changes: 2 additions & 2 deletions docpages/example_code/coro_expiring_buttons.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ int main() {

bot.on_log(dpp::utility::cout_logger());

bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job {
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
if (event.command.get_command_name() == "test") {
// Make a message and add a button with its custom ID set to the command interaction's ID so we can identify it
dpp::message m{"Test"};
Expand Down Expand Up @@ -38,7 +38,7 @@ int main() {
}
});

bot.on_ready([&bot](const dpp::ready_t & event) {
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
dpp::slashcommand command{"test", "Test awaiting for an event", bot.me.id};

Expand Down
3 changes: 1 addition & 2 deletions docpages/example_code/coro_intro.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ int main() {
bot.on_log(dpp::utility::cout_logger());

/* The event is fired when someone issues your commands */
/* Make note of passing the event by value, this is important (explained below) */
bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job {
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
if (event.command.get_command_name() == "file") {
/* Request the image from the URL specified and co_await the response */
dpp::http_request_completion_t result = co_await event.from->creator->co_request("https://dpp.dev/DPP-Logo.png", dpp::m_get);
Expand Down
4 changes: 2 additions & 2 deletions docpages/example_code/coro_simple_commands1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ int main() {

bot.on_log(dpp::utility::cout_logger());

bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job {
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
if (event.command.get_command_name() == "addemoji") {
dpp::cluster *cluster = event.from->creator;
// Retrieve parameter values
Expand Down Expand Up @@ -48,7 +48,7 @@ int main() {
}
});

bot.on_ready([&bot](const dpp::ready_t & event) {
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
dpp::slashcommand command("addemoji", "Add an emoji", bot.me.id);
// Add file and name as required parameters
Expand Down
4 changes: 2 additions & 2 deletions docpages/example_code/coro_simple_commands2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ int main() {

bot.on_log(dpp::utility::cout_logger());

bot.on_slashcommand([](dpp::slashcommand_t event) -> dpp::job {
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
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<std::optional<dpp::guild_member>> {
Expand Down Expand Up @@ -70,7 +70,7 @@ int main() {
});


bot.on_ready([&bot](const dpp::ready_t & event) {
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
dpp::slashcommand command("avatar", "Get your or another user's avatar image", bot.me.id);
command.add_option(dpp::command_option(dpp::co_user, "user", "User to fetch the avatar from"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Let's revisit \ref attach-file "attaching a downloaded file", but this time with

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. Coroutines that use dpp::job can be used for event handlers, they can be attached to an event router just the same way as regular event handlers.
In order to be a coroutine, a function has to return a special type with special functions; D++ offers dpp::job, dpp::task, and dpp::coroutine, which are designed to work seamlessly with asynchronous calls through dpp::async, which all the functions starting with `co_` such as dpp::cluster::co_message_create return. Event routers can have a dpp::task coroutine attached to them, as this object allows to create coroutines that can execute on their own, asynchronously. More on that and the difference between it and the other two types later. To turn a function into a coroutine, simply make it return dpp::task<void> as seen in the example at line 10, then use `co_await` on awaitable types or `co_return`. The moment the execution encounters one of these two keywords, the function is transformed into a coroutine. Coroutines that use dpp::task<void> can be used for event handlers, they can be attached to an event router just the same way as regular event handlers.

When using a `co_*` function such as `co_message_create`, the request is sent immediately and the returned dpp::async can be `co_await`-ed, at which point the coroutine suspends (pauses) and returns back to its caller; in other words, the program is free to go and do other things while the data is being retrieved and D++ will resume your coroutine when it has the data you need, which will be returned from the `co_await` expression.

Expand Down
3 changes: 0 additions & 3 deletions include/dpp/coro.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@
*
************************************************************************************/

#ifdef DPP_CORO
#pragma once

#include "coro/async.h"
#include "coro/coroutine.h"
#include "coro/job.h"
#include "coro/task.h"
#include "coro/when_any.h"

#endif /* DPP_CORO */
14 changes: 13 additions & 1 deletion include/dpp/coro/async.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,19 @@
* limitations under the License.
*
************************************************************************************/
#pragma once

#include <dpp/utility.h>

namespace dpp {

struct async_dummy {
int* dummy_shared_state = nullptr;
};

}

#ifdef DPP_CORO
#pragma once

#include "coro.h"

Expand Down Expand Up @@ -490,6 +500,8 @@ class async : private detail::async::async_base<R> {
}
};

DPP_CHECK_ABI_COMPAT(async<>, async_dummy);

} // namespace dpp

#endif /* DPP_CORO */
15 changes: 14 additions & 1 deletion include/dpp/coro/coroutine.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,19 @@
* limitations under the License.
*
************************************************************************************/
#pragma once

#include <dpp/utility.h>

namespace dpp {

struct coroutine_dummy {
int *handle_dummy = nullptr;
};

}

#ifdef DPP_CORO
#pragma once

#include "coro.h"

Expand Down Expand Up @@ -562,6 +572,9 @@ inline void coroutine<void>::await_resume_impl() const {
}
#endif /* _DOXYGEN_ */

DPP_CHECK_ABI_COMPAT(coroutine<void>, coroutine_dummy)
DPP_CHECK_ABI_COMPAT(coroutine<uint64_t>, coroutine_dummy)

} // namespace dpp

/**
Expand Down
40 changes: 11 additions & 29 deletions include/dpp/coro/job.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,18 @@
* limitations under the License.
*
************************************************************************************/
#pragma once

#include <dpp/utility.h>

namespace dpp {

struct job_dummy {
};

}

#ifdef DPP_CORO
#pragma once

#include "coro.h"

Expand Down Expand Up @@ -49,15 +58,6 @@ namespace detail {

namespace job {

template <typename... Args>
inline constexpr bool coroutine_has_no_ref_params_v = false;

template <>
inline constexpr bool coroutine_has_no_ref_params_v<> = true;

template <typename T, typename... Args>
inline constexpr bool coroutine_has_no_ref_params_v<T, Args...> = (std::is_invocable_v<T, Args...> || !std::is_reference_v<T>) && (!std::is_reference_v<Args> && ... && true);

#ifdef DPP_CORO_TEST
struct promise{};
#endif
Expand Down Expand Up @@ -118,31 +118,13 @@ struct promise {
* @brief Function called when the job returns. Does nothing.
*/
void return_void() const noexcept {}

/**
* @brief Function that will wrap every co_await inside of the job.
*/
template <typename T>
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(coroutine_has_no_ref_params_v<Args...>, "co_await is disabled in dpp::job when taking parameters by reference. read comment above this line for more info");

return std::forward<T>(expr);
}
};

} // namespace job

} // namespace detail

DPP_CHECK_ABI_COMPAT(job, job_dummy)
} // namespace dpp

/**
Expand Down
15 changes: 14 additions & 1 deletion include/dpp/coro/task.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,19 @@
* limitations under the License.
*
************************************************************************************/
#pragma once

#include <dpp/utility.h>

namespace dpp {

struct task_dummy {
int* handle_dummy = nullptr;
};

}

#ifdef DPP_CORO
#pragma once

#include "coro.h"

Expand Down Expand Up @@ -747,6 +757,9 @@ inline void task<void>::await_resume_impl() const {
}
#endif /* _DOXYGEN_ */

DPP_CHECK_ABI_COMPAT(task<void>, task_dummy)
DPP_CHECK_ABI_COMPAT(task<uint64_t>, task_dummy)

} // namespace dpp

/**
Expand Down
Loading

0 comments on commit e72ac76

Please sign in to comment.