Skip to content

Commit c0e8e13

Browse files
committed
refactor: dpp::awaitable now lazily starts on co_await, dpp::async now for parallel awaitable requests
1 parent af6e140 commit c0e8e13

File tree

9 files changed

+370
-239
lines changed

9 files changed

+370
-239
lines changed

buildtools/classes/Generator/CoroGenerator.php

+5-3
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ public function generateHeaderDef(string $returnType, string $currentFunction, s
9898
*/
9999
public function generateCppDef(string $returnType, string $currentFunction, string $parameters, string $noDefaults, string $parameterTypes, string $parameterNames): string
100100
{
101-
return "awaitable<confirmation_callback_t> cluster::co_${currentFunction}($noDefaults) {\n\treturn {this, static_cast<void (cluster::*)($parameterTypes". (!empty($parameterTypes) ? ", " : "") . "command_completion_event_t)>(&cluster::$currentFunction)$parameterNames};\n}\n\n";
101+
if (substr($parameterNames, 0, 2) === ", ")
102+
$parameterNames = substr($parameterNames, 2);
103+
return "awaitable<confirmation_callback_t> cluster::co_${currentFunction}($noDefaults) {\n\treturn [=, this] (auto &&cc) { this->$currentFunction($parameterNames" . (empty($parameterNames) ? "": ", ") . "cc); };\n}\n\n";
102104
}
103105

104106
/**
@@ -114,7 +116,7 @@ public function getCommentArray(): array
114116
*/
115117
public function saveHeader(string $content): void
116118
{
117-
$content .= "awaitable<http_request_completion_t> co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", const std::multimap<std::string, std::string> &headers = {});\n\n";
119+
$content .= "awaitable<http_request_completion_t> co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", std::multimap<std::string, std::string> headers = {});\n\n";
118120
file_put_contents('include/dpp/cluster_coro_calls.h', $content);
119121
}
120122

@@ -123,7 +125,7 @@ public function saveHeader(string $content): void
123125
*/
124126
public function saveCpp(string $cppcontent): void
125127
{
126-
$cppcontent .= "dpp::awaitable<dpp::http_request_completion_t> dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap<std::string, std::string> &headers) {\n\treturn awaitable<http_request_completion_t>{[&](auto &&cc) { this->request(url, method, cc, postdata, mimetype, headers); }};\n}
128+
$cppcontent .= "dpp::awaitable<dpp::http_request_completion_t> dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, std::multimap<std::string, std::string> headers) {\n\treturn awaitable<http_request_completion_t>{[=, this, h = std::move(headers)](auto &&cc) { this->request(url, method, cc, postdata, mimetype, h); }};\n}
127129
128130
#endif
129131
";

include/dpp/cluster.h

+7-7
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,12 @@ class DPP_EXPORT cluster {
350350

351351
#ifdef DPP_CORO
352352
/**
353-
* @brief Start a one-time timer. Use the co_await keyword on its return value to suspend the coroutine until the timer ends
354-
*
355-
* @param seconds How long to run the timer for
356-
* @return awaitable<timer> co_await-able object holding the timer_handle
353+
* @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
354+
*
355+
* @param seconds How long to wait for
356+
* @return awaitable<timer> Object that can be co_await-ed to suspend the function for a certain time
357357
*/
358-
awaitable<timer> co_timer(uint64_t seconds);
358+
awaitable<timer> co_sleep(uint64_t seconds);
359359
#endif
360360

361361
/**
@@ -3241,7 +3241,7 @@ class DPP_EXPORT cluster {
32413241
* @param callback Function to call when the API call completes.
32423242
* 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().
32433243
*/
3244-
void guild_sticker_create(sticker &s, command_completion_event_t callback = utility::log_error());
3244+
void guild_sticker_create(const sticker &s, command_completion_event_t callback = utility::log_error());
32453245

32463246
/**
32473247
* @brief Modify a sticker in a guild
@@ -3251,7 +3251,7 @@ class DPP_EXPORT cluster {
32513251
* @param callback Function to call when the API call completes.
32523252
* 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().
32533253
*/
3254-
void guild_sticker_modify(sticker &s, command_completion_event_t callback = utility::log_error());
3254+
void guild_sticker_modify(const sticker &s, command_completion_event_t callback = utility::log_error());
32553255

32563256
/**
32573257
* @brief Delete a sticker from a guild

include/dpp/cluster_coro_calls.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -1742,7 +1742,7 @@ awaitable<confirmation_callback_t> co_stage_instance_delete(const snowflake chan
17421742
* @return sticker returned object on completion
17431743
* \memberof dpp::cluster
17441744
*/
1745-
awaitable<confirmation_callback_t> co_guild_sticker_create(sticker &s);
1745+
awaitable<confirmation_callback_t> co_guild_sticker_create(const sticker &s);
17461746

17471747
/**
17481748
* @brief Delete a sticker from a guild
@@ -1776,7 +1776,7 @@ awaitable<confirmation_callback_t> co_guild_sticker_get(snowflake id, snowflake
17761776
* @return sticker returned object on completion
17771777
* \memberof dpp::cluster
17781778
*/
1779-
awaitable<confirmation_callback_t> co_guild_sticker_modify(sticker &s);
1779+
awaitable<confirmation_callback_t> co_guild_sticker_modify(const sticker &s);
17801780

17811781
/**
17821782
* @brief Get all guild stickers
@@ -2390,5 +2390,5 @@ awaitable<confirmation_callback_t> co_get_webhook_with_token(snowflake webhook_i
23902390

23912391

23922392
/* End of auto-generated definitions */
2393-
awaitable<http_request_completion_t> co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap<std::string, std::string> &headers = {});
2393+
awaitable<http_request_completion_t> co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", std::multimap<std::string, std::string> headers = {});
23942394

include/dpp/coro.h

+133-12
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace std {
2323

2424
#include <memory>
2525
#include <utility>
26+
#include <tuple>
2627
#include <type_traits>
2728
#include <concepts>
2829
#include <optional>
@@ -174,7 +175,7 @@ namespace dpp {
174175
* @return bool Whether not to suspend the caller or not
175176
*/
176177
bool await_ready() {
177-
return handle.done();
178+
return handle.promise().is_sync;
178179
}
179180

180181
/**
@@ -192,8 +193,13 @@ namespace dpp {
192193

193194
if (my_promise.is_sync)
194195
return false;
196+
197+
std::lock_guard lock{my_promise.mutex};
198+
199+
if (handle.done())
200+
return (false);
195201
my_promise.parent = caller;
196-
caller.promise().is_sync = false;
202+
caller.promise().is_sync = false;
197203
return true;
198204
}
199205

@@ -414,12 +420,117 @@ namespace dpp {
414420
if (handle.promise().exception) // If we have an exception, rethrow
415421
std::rethrow_exception(handle.promise().exception);
416422
if constexpr (!std::is_same_v<ReturnType, void>) // If we have a return type, return it and clean up our stored value
417-
return std::forward<ReturnType>(*std::exchange(handle.promise().value, std::nullopt));
423+
return *std::exchange(handle.promise().value, std::nullopt);
418424
}
419425

426+
template <typename ReturnType = confirmation_callback_t>
427+
class async;
428+
420429
/**
421430
* @brief A co_await-able object handling an API call.
422431
*
432+
* @remark - The coroutine may be resumed in another thread, do not rely on thread_local variables.
433+
* @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub issues</a> or to the <a href="https://discord.gg/dpp">D++ Discord server</a>.
434+
* @tparam ReturnType The return type of the API call. Defaults to confirmation_callback_t
435+
*/
436+
template <typename ReturnType = confirmation_callback_t>
437+
struct awaitable {
438+
/**
439+
* @brief Construct an awaitable object from a callable. This can be used to manually wrap an async call.
440+
*
441+
* Callable should be an invokeable object, taking a parameter that is the callback to be passed to the async call.
442+
* For example : `[cluster, message](auto &&cb) { cluster->message_create(message, cb); }
443+
*
444+
* @warning This callback is to be executed <b>later</b>, on co_await. <a href="/lambdas-and-locals.html">Be mindful of reference captures</a>.
445+
*/
446+
awaitable(std::invocable<std::function<void(ReturnType)>> auto &&fun) : callable{fun} {}
447+
448+
/**
449+
* @brief Awaitable is copyable.
450+
*/
451+
awaitable(const awaitable&) = default;
452+
453+
/**
454+
* @brief Awaitable is moveable.
455+
*/
456+
awaitable(awaitable&&) noexcept = default;
457+
458+
/**
459+
* @brief Awaitable is copyable.
460+
*/
461+
awaitable& operator=(const awaitable&) = default;
462+
463+
/**
464+
* @brief Awaitable is moveable.
465+
*/
466+
awaitable& operator=(awaitable&&) noexcept = default;
467+
468+
using request_fun_t = std::function<void(std::function<void(ReturnType)>)>;
469+
470+
/**
471+
* @brief Callable object that will be responsible for the API call when co_await-ing.
472+
*/
473+
request_fun_t callable;
474+
475+
/**
476+
* @brief Awaitable object returned by operator co_await.
477+
*
478+
* Do not use this directly, it is designed to be used with co_await.
479+
*/
480+
struct awaiter {
481+
/**
482+
* @brief Reference to the callable object that will be responsible for the API call when co_await-ing.
483+
*/
484+
const request_fun_t& fun;
485+
486+
/**
487+
* @brief Optional containing the result of the API call.
488+
*/
489+
std::optional<ReturnType> result = std::nullopt;
490+
491+
/**
492+
* @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.
493+
*
494+
* @return false Always return false, we send the API call on suspend.
495+
*/
496+
bool await_ready() const noexcept {
497+
return false;
498+
}
499+
500+
/**
501+
* @brief Second function called by the standard library when this object is co_await-ed. Suspends and sends the API call.
502+
*/
503+
template <typename T>
504+
void await_suspend(detail::std_coroutine::coroutine_handle<T> caller) noexcept(noexcept(std::invoke(fun, std::declval<std::function<void(ReturnType)>&&>()))) {
505+
if constexpr (requires (T promise) {{promise.is_sync} -> std::same_as<bool&>;})
506+
caller.promise().is_sync = false;
507+
std::invoke(fun, [this, caller](auto &&api_result) {
508+
result = api_result;
509+
caller.resume();
510+
});
511+
}
512+
513+
/**
514+
* @brief Function called by the standard library when the handle is resumed. Returns the API result as an rvalue.
515+
*/
516+
ReturnType await_resume() {
517+
return *std::exchange(result, std::nullopt);
518+
}
519+
};
520+
521+
/**
522+
* @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.
523+
*
524+
* In contrast with dpp::async, it is fine to co_await this object more than once.
525+
*/
526+
auto operator co_await() const noexcept {
527+
return awaiter{callable};
528+
}
529+
};
530+
531+
/**
532+
* @brief A co_await-able object handling an API call in parallel with the caller.
533+
*
423534
* This class is the return type of the dpp::cluster::co_* methods, but it can also be created manually to wrap any async call.
424535
*
425536
* @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,8 +539,8 @@ namespace dpp {
428539
* @warning This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub issues</a> or to the <a href="https://discord.gg/dpp">D++ Discord server</a>.
429540
* @tparam ReturnType The return type of the API call. Defaults to confirmation_callback_t
430541
*/
431-
template <typename ReturnType = confirmation_callback_t>
432-
class awaitable {
542+
template <typename ReturnType>
543+
class async {
433544
/**
434545
* @brief Ref-counted callback, contains the callback logic and manages the lifetime of the callback data over multiple threads.
435546
*/
@@ -596,7 +707,7 @@ namespace dpp {
596707
#ifndef _DOXYGEN_
597708
requires std::invocable<Fun, Obj, Args..., std::function<void(ReturnType)>>
598709
#endif
599-
awaitable(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} {
710+
async(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} {
600711
std::invoke(std::forward<Fun>(fun), std::forward<Obj>(obj), std::forward<Args>(args)..., api_callback);
601712
}
602713

@@ -610,22 +721,31 @@ namespace dpp {
610721
#ifndef _DOXYGEN_
611722
requires std::invocable<Fun, Args..., std::function<void(ReturnType)>>
612723
#endif
613-
awaitable(Fun &&fun, Args&&... args) : api_callback{} {
724+
async(Fun &&fun, Args&&... args) : api_callback{} {
614725
std::invoke(std::forward<Fun>(fun), std::forward<Args>(args)..., api_callback);
615726
}
616727

728+
/**
729+
* @brief Construct an async wrapping an awaitable, the call is made immediately by forwarding to <a href="https://en.cppreference.com/w/cpp/utility/functional/invoke">std::invoke</a> and can be awaited later to retrieve the result.
730+
*
731+
* @param callable The awaitable object whose API call to execute.
732+
*/
733+
async(const awaitable<ReturnType> &awaitable) : api_callback{} {
734+
std::invoke(awaitable.callable, api_callback);
735+
}
736+
617737
/**
618738
* @brief Destructor. If any callback is pending it will be aborted.
619739
*
620740
*/
621-
~awaitable() {
741+
~async() {
622742
api_callback.set_dangling();
623743
}
624744

625745
/**
626746
* @brief Copy constructor is disabled
627747
*/
628-
awaitable(const awaitable &) = delete;
748+
async(const async &) = delete;
629749

630750
/**
631751
* @brief Move constructor
@@ -635,12 +755,12 @@ namespace dpp {
635755
* @remark Using the moved-from awaitable after this function is undefined behavior.
636756
* @param other The awaitable object to move the data from.
637757
*/
638-
awaitable(awaitable &&other) noexcept = default;
758+
async(async &&other) noexcept = default;
639759

640760
/**
641761
* @brief Copy assignment is disabled
642762
*/
643-
awaitable &operator=(const awaitable &) = delete;
763+
async &operator=(const async &) = delete;
644764

645765
/**
646766
* @brief Move assignment operator.
@@ -650,7 +770,7 @@ namespace dpp {
650770
* @remark Using the moved-from awaitable after this function is undefined behavior.
651771
* @param other The awaitable object to move the data from
652772
*/
653-
awaitable &operator=(awaitable &&other) noexcept = default;
773+
async &operator=(async &&other) noexcept = default;
654774

655775
/**
656776
* @brief First function called by the standard library when the object is co-awaited.
@@ -692,6 +812,7 @@ namespace dpp {
692812
* @return ReturnType The result of the API call.
693813
*/
694814
ReturnType await_resume() {
815+
// no locking needed here as the callback has already executed
695816
return std::move(*api_callback.get_result());
696817
}
697818
};

include/dpp/event_router.h

+25-17
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ template<class T> class event_router_t {
108108
*
109109
* Note: keep a listener's parameter as a value type, the event passed can die while a coroutine is suspended
110110
*/
111-
std::map<event_handle, std::function<dpp::task<void>(T)>> coroutine_container;
111+
std::map<event_handle, std::function<dpp::task<void>(const T&)>> coroutine_container;
112112
#else
113113
#ifndef _DOXYGEN_
114114
/**
@@ -164,22 +164,30 @@ template<class T> class event_router_t {
164164
}
165165
});
166166
#ifdef DPP_CORO
167-
auto coro_exception_handler = [from = event.from](std::exception_ptr ptr) {
168-
try {
169-
std::rethrow_exception(ptr);
170-
}
171-
catch (const std::exception &exception) {
172-
if (from && from->creator)
173-
from->creator->log(dpp::loglevel::ll_error, std::string{"Uncaught exception in event coroutine: "} + exception.what());
174-
}
175-
};
176-
std::for_each(coroutine_container.begin(), coroutine_container.end(), [&](auto &ev) {
177-
if (!event.is_cancelled()) {
178-
dpp::task<void> task = ev.second(event);
167+
if (!coroutine_container.empty()) {
168+
[](const event_router_t<T> *me, T event) -> dpp::task<void> {
169+
std::vector<dpp::task<void>> coroutines;
170+
dpp::cluster *cluster = event.from ? event.from->creator : nullptr;
179171

180-
task.on_exception(coro_exception_handler);
181-
}
182-
});
172+
coroutines.reserve(me->coroutine_container.size());
173+
for (const auto &elem : me->coroutine_container) {
174+
if (event.is_cancelled())
175+
break;
176+
coroutines.emplace_back(elem.second(event));
177+
}
178+
for (auto &coro : coroutines) {
179+
try {
180+
co_await coro;
181+
}
182+
catch (const std::exception &e)
183+
{
184+
if (cluster)
185+
cluster->log(dpp::loglevel::ll_error, std::string{"Uncaught exception in event coroutine: "} + e.what());
186+
}
187+
}
188+
std::cout << "done" << std::endl;
189+
}(this, event);
190+
}
183191
#endif /* DPP_CORO */
184192
};
185193

@@ -252,7 +260,7 @@ template<class T> class event_router_t {
252260
* @return event_handle An event handle unique to this event, used to
253261
* detach the listener from the event later if necessary.
254262
*/
255-
event_handle co_attach(std::function<dpp::task<void>(T)> func) {
263+
event_handle co_attach(std::function<dpp::task<void>(const T&)> func) {
256264
std::unique_lock l(lock);
257265
event_handle h = next_handle++;
258266
coroutine_container.emplace(h, func);

0 commit comments

Comments
 (0)