스킵 구현

This commit is contained in:
2023-12-20 19:45:25 +09:00
parent 6503fd167b
commit 8a987320e0
775 changed files with 162601 additions and 135 deletions

View File

@@ -0,0 +1,523 @@
/************************************************************************************
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/utility.h>
namespace dpp {
struct async_dummy {
int* dummy_shared_state = nullptr;
};
}
#ifdef DPP_CORO
#include "coro.h"
#include <mutex>
#include <utility>
#include <type_traits>
#include <functional>
#include <atomic>
#include <cstddef>
namespace dpp {
namespace detail {
/**
* @brief Empty struct used for overload resolution.
*/
struct empty_tag_t{};
namespace async {
/**
* @brief Represents the step an std::async is at.
*/
enum class state_t {
/**
* @brief Request was sent but not co_await-ed. handle is nullptr, result_storage is not constructed.
*/
sent,
/**
* @brief Request was co_await-ed. handle is valid, result_storage is not constructed.
*/
waiting,
/**
* @brief Request was completed. handle is unknown, result_storage is valid.
*/
done,
/**
* @brief Request was never co_await-ed.
*/
dangling
};
/**
* @brief State of the async and its callback.
*
* Defined outside of dpp::async because this seems to work better with Intellisense.
*/
template <typename R>
struct async_callback_data {
/**
* @brief Number of references to this callback state.
*/
std::atomic<int> ref_count{1};
/**
* @brief State of the awaitable and the API callback
*/
std::atomic<state_t> state = 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<std::byte, sizeof(R)> result_storage;
/**
* @brief Handle to the coroutine co_await-ing on this API call
*
* @see <a href="https://en.cppreference.com/w/cpp/coroutine/coroutine_handle">std::coroutine_handle</a>
*/
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 <typename... Ts>
void construct_result(Ts&&... ts) {
// Standard-compliant type punning yay
std::construct_at<R>(reinterpret_cast<R *>(result_storage.data()), std::forward<Ts>(ts)...);
}
/**
* @brief Destructor.
*
* Also destroys the result if present.
*/
~async_callback_data() {
if (state.load() == state_t::done) {
std::destroy_at<R>(reinterpret_cast<R *>(result_storage.data()));
}
}
};
/**
* @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 <typename R>
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 Self-managed ref-counted pointer to the state data
*/
async_callback_data<R> *state = new async_callback_data<R>;
/**
* @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
*/
template <std::convertible_to<R> V>
void operator()(V &&cback) const {
state->construct_result(std::forward<V>(cback));
if (auto previous_state = state->state.exchange(state_t::done); previous_state == state_t::waiting) {
state->coro_handle.resume();
}
}
/**
* @brief Main constructor, allocates a new callback_state object.
*/
shared_callback() = default;
/**
* @brief Empty constructor, holds no 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) noexcept {
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 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 {
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 {
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() noexcept {
if (!state) { // moved-from object
return;
}
state->state.store(state_t::dangling);
}
bool done(std::memory_order order = std::memory_order_seq_cst) const noexcept {
return (state->state.load(order) == state_t::done);
}
/**
* @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 state_t::done.
*/
R &get_result() noexcept {
assert(state && done());
return (*reinterpret_cast<R *>(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 state_t::done.
*/
const R &get_result() const noexcept {
assert(state && done());
return (*reinterpret_cast<R *>(state->result_storage.data()));
}
};
/**
* @brief Shared state of the async and its callback, to be used across threads.
*/
shared_callback api_callback{nullptr};
public:
/**
* @brief Construct an async object wrapping an object method, 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.
*
* @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 <typename Obj, typename Fun, typename... Args>
#ifndef _DOXYGEN_
requires std::invocable<Fun, Obj, Args..., std::function<void(R)>>
#endif
explicit async_base(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} {
std::invoke(std::forward<Fun>(fun), std::forward<Obj>(obj), std::forward<Args>(args)..., api_callback);
}
/**
* @brief Construct an async object wrapping an invokeable object, 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.
*
* @param fun The object to call using <a href="https://en.cppreference.com/w/cpp/utility/functional/invoke">std::invoke</a>. 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 <typename Fun, typename... Args>
#ifndef _DOXYGEN_
requires std::invocable<Fun, Args..., std::function<void(R)>>
#endif
explicit async_base(Fun &&fun, Args&&... args) : api_callback{} {
std::invoke(std::forward<Fun>(fun), std::forward<Args>(args)..., api_callback);
}
/**
* @brief Construct an empty async. Using `co_await` on an empty async is undefined behavior.
*/
async_base() noexcept : api_callback{detail::empty_tag_t{}} {}
/**
* @brief Destructor. If any callback is pending it will be aborted.
*/
~async_base() {
api_callback.set_dangling();
}
/**
* @brief Copy constructor is disabled
*/
async_base(const async_base &) = 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_base(async_base &&other) noexcept = default;
/**
* @brief Copy assignment is disabled
*/
async_base &operator=(const async_base &) = 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_base &operator=(async_base &&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
*/
[[nodiscard]] bool await_ready() const noexcept {
return api_callback.done();
}
/**
* @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 caller The handle to the coroutine co_await-ing and being suspended
*/
[[nodiscard]] bool await_suspend(detail::std_coroutine::coroutine_handle<> caller) noexcept {
auto sent = state_t::sent;
api_callback.state->coro_handle = caller;
return api_callback.state->state.compare_exchange_strong(sent, 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 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 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 The result of the API call as an rvalue reference.
*/
R&& await_resume() && noexcept {
return std::move(api_callback.get_result());
}
};
} // namespace async
} // namespace detail
struct confirmation_callback_t;
/**
* @class async async.h coro/async.h
* @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 <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub issues</a> or to the <a href="https://discord.gg/dpp">D++ Discord server</a>.
* @tparam R The return type of the API call. Defaults to confirmation_callback_t
*/
template <typename R>
class async : private detail::async::async_base<R> {
/**
* @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::async_base<R>;
public:
using detail::async::async_base<R>::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::async_base<R>::operator=; // use async_base's assignment operator
using detail::async::async_base<R>::await_ready; // expose await_ready as public
/**
* @brief Construct an async object wrapping an object method, 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.
*
* @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 <typename Obj, typename Fun, typename... Args>
#ifndef _DOXYGEN_
requires std::invocable<Fun, Obj, Args..., std::function<void(R)>>
#endif
explicit async(Obj &&obj, Fun &&fun, Args&&... args) : detail::async::async_base<R>{std::forward<Obj>(obj), std::forward<Fun>(fun), std::forward<Args>(args)...} {}
/**
* @brief Construct an async object wrapping an invokeable object, 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.
*
* @param fun The object to call using <a href="https://en.cppreference.com/w/cpp/utility/functional/invoke">std::invoke</a>. 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 <typename Fun, typename... Args>
#ifndef _DOXYGEN_
requires std::invocable<Fun, Args..., std::function<void(R)>>
#endif
explicit async(Fun &&fun, Args&&... args) : detail::async::async_base<R>{std::forward<Fun>(fun), std::forward<Args>(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
*/
[[nodiscard]] bool await_ready() const noexcept;
#endif
/**
* @brief Suspend the caller until the request completes.
*
* @return On resumption, this expression evaluates to the result object of type R, as a reference.
*/
[[nodiscard]] auto& operator co_await() & noexcept {
return static_cast<detail::async::async_base<R>&>(*this);
}
/**
* @brief Suspend the caller until the request completes.
*
* @return On resumption, this expression evaluates to the result object of type R, as a const reference.
*/
[[nodiscard]] const auto& operator co_await() const & noexcept {
return static_cast<detail::async::async_base<R> const&>(*this);
}
/**
* @brief Suspend the caller until the request completes.
*
* @return On resumption, this expression evaluates to the result object of type R, as an rvalue reference.
*/
[[nodiscard]] auto&& operator co_await() && noexcept {
return static_cast<detail::async::async_base<R>&&>(*this);
}
};
DPP_CHECK_ABI_COMPAT(async<>, async_dummy);
} // namespace dpp
#endif /* DPP_CORO */

View File

@@ -0,0 +1,186 @@
/************************************************************************************
*
* 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 <experimental/coroutine>
#else
# include <coroutine>
#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
#ifndef _DOXYGEN_
/**
* @brief Concept to check if a type has a useable `operator co_await()` member
*/
template <typename T>
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 <typename T>
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 <typename T>
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 <typename T>
requires (has_co_await_member<T>)
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 <typename T>
requires (!has_co_await_member<T> && has_free_co_await<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept(noexcept(operator co_await(expr))) {
decltype(auto) awaiter = operator co_await(static_cast<T&&>(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 <typename T>
requires (!has_co_await_member<T> && !has_free_co_await<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept {
return static_cast<T&&>(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 <typename T>
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 <typename T>
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 <typename T>
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
/**
* @brief Convenience alias for the result of a certain awaitable's await_resume.
*/
template <typename T>
using awaitable_result = decltype(co_await_resolve(std::declval<T>()).await_resume());
} // namespace detail
struct confirmation_callback_t;
template <typename R = confirmation_callback_t>
class async;
template <typename R = void>
#ifndef _DOXYGEN_
requires (!std::is_reference_v<R>)
#endif
class task;
template <typename R = void>
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 <typename T>
inline int coro_alloc_count = 0;
#endif
} // namespace dpp
#endif /* DPP_CORO */

View File

@@ -0,0 +1,589 @@
/************************************************************************************
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/utility.h>
namespace dpp {
struct coroutine_dummy {
int *handle_dummy = nullptr;
};
}
#ifdef DPP_CORO
#include "coro.h"
#include <optional>
#include <type_traits>
#include <exception>
#include <utility>
#include <type_traits>
namespace dpp {
namespace detail {
namespace coroutine {
template <typename R>
struct promise_t;
template <typename R>
/**
* @brief Alias for the handle_t of a coroutine.
*/
using handle_t = std_coroutine::coroutine_handle<promise_t<R>>;
/**
* @brief Base class of dpp::coroutine<R>.
*
* @warn This class should not be used directly by a user, use dpp::coroutine<R> instead.
* @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::coroutine<R> so a user cannot call await_suspend and await_resume directly.
*/
template <typename R>
class coroutine_base {
protected:
/**
* @brief Promise has friend access for the constructor
*/
friend struct promise_t<R>;
/**
* @brief Coroutine handle.
*/
detail::coroutine::handle_t<R> handle{nullptr};
private:
/**
* @brief Construct from a handle. Internal use only.
*/
coroutine_base(detail::coroutine::handle_t<R> 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
*/
[[nodiscard]] 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 <typename T>
[[nodiscard]] handle_t<R> await_suspend(detail::std_coroutine::coroutine_handle<T> 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<dpp::coroutine<R> &>(*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`
*/
[[nodiscard]] decltype(auto) await_resume() const & {
return static_cast<dpp::coroutine<R> 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`
*/
[[nodiscard]] decltype(auto) await_resume() && {
return static_cast<dpp::coroutine<R> &&>(*this).await_resume_impl();
}
};
} // namespace coroutine
} // namespace detail
/**
* @class coroutine coroutine.h coro/coroutine.h
* @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 <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub Issues</a> or to our <a href="https://discord.gg/dpp">Discord Server</a>.
* @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 <typename R>
class coroutine : private detail::coroutine::coroutine_base<R> {
/**
* @brief Internal use only base class containing common logic between coroutine<R> and coroutine<void>. It also serves to prevent await_suspend and await_resume from being used directly.
*
* @warning For internal use only, do not use.
* @see operator co_await()
*/
friend class detail::coroutine::coroutine_base<R>;
[[nodiscard]] R& await_resume_impl() & {
detail::coroutine::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *promise.result;
}
[[nodiscard]] const R& await_resume_impl() const & {
detail::coroutine::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *promise.result;
}
[[nodiscard]] R&& await_resume_impl() && {
detail::coroutine::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *std::move(promise.result);
}
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
*/
[[nodiscard]] bool await_ready() const;
#else
using detail::coroutine::coroutine_base<R>::coroutine_base; // use coroutine_base's constructors
using detail::coroutine::coroutine_base<R>::operator=; // use coroutine_base's assignment operators
using detail::coroutine::coroutine_base<R>::await_ready; // expose await_ready as public
#endif
/**
* @brief Suspend the caller until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a reference.
*/
[[nodiscard]] auto& operator co_await() & noexcept {
return static_cast<detail::coroutine::coroutine_base<R>&>(*this);
}
/**
* @brief Suspend the caller until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a const reference.
*/
[[nodiscard]] const auto& operator co_await() const & noexcept {
return static_cast<detail::coroutine::coroutine_base<R> 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 On resumption, this expression evaluates to the result object of type R, as an rvalue reference.
*/
[[nodiscard]] auto&& operator co_await() && noexcept {
return static_cast<detail::coroutine::coroutine_base<R>&&>(*this);
}
};
#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.
*
* @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>.
* @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<void> : private detail::coroutine::coroutine_base<void> {
/**
* @brief Base class has friend access for CRTP downcast
*/
friend class detail::coroutine::coroutine_base<void>;
void await_resume_impl() const;
public:
using detail::coroutine::coroutine_base<void>::coroutine_base; // use coroutine_base's constructors
using detail::coroutine::coroutine_base<void>::operator=; // use coroutine_base's assignment operators
using detail::coroutine::coroutine_base<void>::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 On resumption, this expression evaluates to the result object of type R, as a reference.
*/
[[nodiscard]] auto& operator co_await() & noexcept {
return static_cast<detail::coroutine::coroutine_base<void>&>(*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 On resumption, this expression evaluates to the result object of type R, as a const reference.
*/
[[nodiscard]] const auto& operator co_await() const & noexcept {
return static_cast<detail::coroutine::coroutine_base<void> 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 On resumption, this expression evaluates to the result object of type R, as an rvalue reference.
*/
[[nodiscard]] auto&& operator co_await() && noexcept {
return static_cast<detail::coroutine::coroutine_base<void>&&>(*this);
}
};
#endif /* _DOXYGEN_ */
namespace detail::coroutine {
template <typename R>
struct final_awaiter;
#ifdef DPP_CORO_TEST
struct promise_t_base{};
#endif
/**
* @brief Promise type for coroutine.
*/
template <typename R>
struct promise_t {
/**
* @brief Handle of the coroutine co_await-ing this coroutine.
*/
std_coroutine::coroutine_handle<> parent{nullptr};
/**
* @brief Return value of the coroutine
*/
std::optional<R> result{};
/**
* @brief Pointer to an uncaught exception thrown by the coroutine
*/
std::exception_ptr exception{nullptr};
#ifdef DPP_CORO_TEST
promise_t() {
++coro_alloc_count<promise_t_base>;
}
~promise_t() {
--coro_alloc_count<promise_t_base>;
}
#endif
/**
* @brief Function called by the standard library when reaching the end of a coroutine
*
* @return final_awaiter<R> Resumes any coroutine co_await-ing on this
*/
[[nodiscard]] final_awaiter<R> final_suspend() const noexcept;
/**
* @brief Function called by the standard library when the coroutine start
*
* @return @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_always">std::suspend_always</a> Always suspend at the start, for a lazy start
*/
[[nodiscard]] 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<R>) requires std::move_constructible<R> {
result = static_cast<R&&>(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<R>) requires std::copy_constructible<R> {
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 <typename T>
requires (!std::is_same_v<R, std::remove_cvref_t<T>> && std::convertible_to<T, R>)
void return_value(T&& expr) noexcept (std::is_nothrow_convertible_v<T, R>) {
result = std::forward<T>(expr);
}
/**
* @brief Function called to get the coroutine object
*/
dpp::coroutine<R> get_return_object() {
return dpp::coroutine<R>{handle_t<R>::from_promise(*this)};
}
};
/**
* @brief Struct returned by a coroutine's final_suspend, resumes the continuation
*/
template <typename R>
struct 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
*/
[[nodiscard]] bool await_ready() const noexcept {
return false;
}
/**
* @brief Second function called by the standard library when reaching the end of a coroutine.
*
* @return std::handle_t<> Coroutine handle to resume, this is either the parent if present or std::noop_coroutine()
*/
[[nodiscard]] std_coroutine::coroutine_handle<> await_suspend(std_coroutine::coroutine_handle<promise_t<R>> 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 <typename R>
final_awaiter<R> promise_t<R>::final_suspend() const noexcept {
return {};
}
/**
* @brief Struct returned by a coroutine's final_suspend, resumes the continuation
*/
template <>
struct promise_t<void> {
/**
* @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 final_awaiter<R> Resumes any coroutine co_await-ing on this
*/
[[nodiscard]] final_awaiter<void> final_suspend() const noexcept {
return {};
}
/**
* @brief Function called by the standard library when the coroutine start
*
* @return @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_always">std::suspend_always</a> Always suspend at the start, for a lazy start
*/
[[nodiscard]] 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
*/
[[nodiscard]] dpp::coroutine<void> get_return_object() {
return dpp::coroutine<void>{handle_t<void>::from_promise(*this)};
}
};
} // namespace detail
#ifndef _DOXYGEN_
inline void coroutine<void>::await_resume_impl() const {
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
}
#endif /* _DOXYGEN_ */
DPP_CHECK_ABI_COMPAT(coroutine<void>, coroutine_dummy)
DPP_CHECK_ABI_COMPAT(coroutine<uint64_t>, coroutine_dummy)
} // namespace dpp
/**
* @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function.
*/
template<typename R, typename... Args>
struct dpp::detail::std_coroutine::coroutine_traits<dpp::coroutine<R>, Args...> {
using promise_type = dpp::detail::coroutine::promise_t<R>;
};
#endif /* DPP_CORO */

View File

@@ -0,0 +1,145 @@
/************************************************************************************
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/utility.h>
namespace dpp {
struct job_dummy {
};
}
#ifdef DPP_CORO
#include "coro.h"
#include <type_traits>
#include <utility>
namespace dpp {
/**
* @class job job.h coro/job.h
* @brief Extremely light coroutine object designed to send off a coroutine to execute on its own.
* Can be used in conjunction with coroutine events via @ref dpp::event_router_t::operator()(F&&) "event routers", or on its own.
*
* This object stores no state and is the recommended way to use coroutines if you do not need to co_await the result.
*
* @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 our <a href="https://discord.gg/dpp">Discord Server</a>.
* @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 @ref lambdas-and-locals "dangling references".
* 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 {
namespace job {
#ifdef DPP_CORO_TEST
struct promise{};
#endif
/**
* @brief Coroutine promise type for a job
*/
template <typename... Args>
struct promise {
#ifdef DPP_CORO_TEST
promise() {
++coro_alloc_count<job_promise_base>;
}
~promise() {
--coro_alloc_count<job_promise_base>;
}
#endif
/**
* @brief Function called when the job is done.
*
* @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_never">std::suspend_never</a> 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 <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_never">std::suspend_never</a> 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 {
throw;
}
/**
* @brief Function called when the job returns. Does nothing.
*/
void return_void() const noexcept {}
};
} // namespace job
} // namespace detail
DPP_CHECK_ABI_COMPAT(job, job_dummy)
} // namespace dpp
/**
* @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function.
*/
template<typename... Args>
struct dpp::detail::std_coroutine::coroutine_traits<dpp::job, Args...> {
/**
* @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<Args...>;
};
#endif /* DPP_CORO */

View File

@@ -0,0 +1,784 @@
/************************************************************************************
*
* 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.
*
************************************************************************************/
#pragma once
#include <dpp/utility.h>
namespace dpp {
struct task_dummy {
int* handle_dummy = nullptr;
};
}
#ifdef DPP_CORO
#include "coro.h"
#include <utility>
#include <type_traits>
#include <optional>
#include <functional>
#include <mutex>
#include <exception>
#include <atomic>
#include <iostream> // std::cerr in final_suspend
namespace dpp {
namespace detail {
/* Internal cogwheels for dpp::task */
namespace task {
/**
* @brief State of a task
*/
enum class 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 @ref dpp::task "task"'s promise_t type, with special logic for handling nested tasks.
*
* @tparam R Return type of the task
*/
template <typename R>
struct promise_t;
/**
* @brief The object automatically co_await-ed at the end of a @ref dpp::task "task". Ensures nested coroutine chains are resolved, and the promise_t cleans up if it needs to.
*
* @tparam R Return type of the task
*/
template <typename R>
struct final_awaiter;
/**
* @brief Alias for <a href="https://en.cppreference.com/w/cpp/coroutine/coroutine_handle"std::coroutine_handle</a> for a @ref dpp::task "task"'s @ref promise_t.
*
* @tparam R Return type of the task
*/
template <typename R>
using handle_t = std_coroutine::coroutine_handle<promise_t<R>>;
/**
* @brief Base class of @ref dpp::task.
*
* @warning This class should not be used directly by a user, use @ref dpp::task instead.
* @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of @ref dpp::task so a user cannot call await_suspend() and await_resume() directly.
*/
template <typename R>
class task_base {
protected:
/**
* @brief The coroutine handle of this task.
*/
handle_t<R> handle;
/**
* @brief Promise type of this coroutine. For internal use only, do not use.
*/
friend struct promise_t<R>;
private:
/**
* @brief Construct from a coroutine handle. Internal use only
*/
explicit task_base(handle_t<R> handle_) : handle(handle_) {}
public:
/**
* @brief Default constructor, creates a task not bound to a coroutine.
*/
task_base() = default;
/**
* @brief Copy constructor is disabled
*/
task_base(const task_base &) = delete;
/**
* @brief Move constructor, grabs another task's coroutine handle
*
* @param other Task to move the handle from
*/
task_base(task_base &&other) noexcept : handle(std::exchange(other.handle, nullptr)) {}
/**
* @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_base() {
if (handle) {
promise_t<R> &promise = handle.promise();
state_t previous_state = promise.state.exchange(state_t::dangling);
if (previous_state == state_t::done) {
handle.destroy();
}
else {
cancel();
}
}
}
/**
* @brief Copy assignment is disabled
*/
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_base &operator=(task_base &&other) noexcept {
handle = std::exchange(other.handle, nullptr);
return (*this);
}
/**
* @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
*/
[[nodiscard]] bool await_ready() const {
if (!handle) {
throw dpp::logic_exception{"cannot co_await an empty task"};
}
return handle.promise().state.load() == state_t::done;
}
/**
* @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
*/
[[nodiscard]] bool await_suspend(std_coroutine::coroutine_handle<> caller) noexcept {
promise_t<R> &my_promise = handle.promise();
auto previous_state = state_t::started;
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, state_t::awaited) && previous_state == state_t::done) {
return false;
}
return true;
}
/**
* @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) == state_t::done;
}
/**
* @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.
*
* @return *this
*/
dpp::task<R>& cancel() & noexcept {
handle.promise().cancelled.exchange(true, std::memory_order_relaxed);
return static_cast<dpp::task<R> &>(*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.
*
* @return *this
*/
dpp::task<R>&& cancel() && noexcept {
handle.promise().cancelled.exchange(true, std::memory_order_relaxed);
return static_cast<dpp::task<R> &&>(*this);
}
/**
* @brief Function called by the standard library when resuming.
*
* @return Return value of the coroutine, handed to the caller of co_await.
*/
decltype(auto) await_resume() & {
return static_cast<dpp::task<R> &>(*this).await_resume_impl();
}
/**
* @brief Function called by the standard library when resuming.
*
* @return Return value of the coroutine, handed to the caller of co_await.
*/
decltype(auto) await_resume() const & {
return static_cast<const dpp::task<R> &>(*this).await_resume_impl();
}
/**
* @brief Function called by the standard library when resuming.
*
* @return Return value of the coroutine, handed to the caller of co_await.
*/
decltype(auto) await_resume() && {
return static_cast<dpp::task<R> &&>(*this).await_resume_impl();
}
};
} // namespace task
} // namespace detail
/**
* @class task task.h coro/task.h
* @brief A coroutine task. It starts immediately on construction and can be co_await-ed, making it perfect for parallel coroutines returning a value.
*
* @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 our <a href="https://discord.gg/dpp">Discord Server</a>.
* @tparam R Return type of the task. Cannot be a reference but can be void.
*/
template <typename R>
#ifndef _DOXYGEN_
requires (!std::is_reference_v<R>)
#endif
class task : private detail::task::task_base<R> {
/**
* @brief Internal use only base class containing common logic between task<R> and task<void>. 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::task_base<R>;
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @throw Throws any exception thrown or uncaught by the coroutine
* @return The result of the coroutine. This is returned to the awaiter as the result of co_await
*/
R& await_resume_impl() & {
detail::task::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *reinterpret_cast<R *>(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 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_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *reinterpret_cast<R *>(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 The result of the coroutine. This is returned to the awaiter as the result of co_await
*/
R&& await_resume_impl() && {
detail::task::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *reinterpret_cast<R *>(promise.result_storage.data());
}
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<R>& 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
*/
[[nodiscard]] bool await_ready() const;
#else
using detail::task::task_base<R>::task_base; // use task_base's constructors
using detail::task::task_base<R>::operator=; // use task_base's assignment operators
using detail::task::task_base<R>::done; // expose done() as public
using detail::task::task_base<R>::cancel; // expose cancel() as public
using detail::task::task_base<R>::await_ready; // expose await_ready as public
#endif
/**
* @brief Suspend the current coroutine until the task completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a reference.
*/
[[nodiscard]] auto& operator co_await() & noexcept {
return static_cast<detail::task::task_base<R>&>(*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 On resumption, this expression evaluates to the result object of type R, as a const reference.
*/
[[nodiscard]] const auto& operator co_await() const & noexcept {
return static_cast<const detail::task::task_base<R>&>(*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 On resumption, this expression evaluates to the result object of type R, as an rvalue reference.
*/
[[nodiscard]] auto&& operator co_await() && noexcept {
return static_cast<detail::task::task_base<R>&&>(*this);
}
};
#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.
*
* 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 <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub issues</a> or to the <a href="https://discord.gg/dpp">D++ Discord server</a>.
* @tparam R Return type of the coroutine. Cannot be a reference, can be void.
*/
template <>
class task<void> : private detail::task::task_base<void> {
/**
* @brief Private base class containing common logic between task<R> and task<void>. It also serves to prevent await_suspend and await_resume from being used directly.
*
* @see operator co_await()
*/
friend class detail::task::task_base<void>;
/**
* @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
*/
void await_resume_impl() const;
public:
using detail::task::task_base<void>::task_base; // use task_base's constructors
using detail::task::task_base<void>::operator=; // use task_base's assignment operators
using detail::task::task_base<void>::done; // expose done() as public
using detail::task::task_base<void>::cancel; // expose cancel() as public
using detail::task::task_base<void>::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 On resumption, returns a reference to the contained result.
*/
auto& operator co_await() & {
return static_cast<detail::task::task_base<void>&>(*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 On resumption, returns a const reference to the contained result.
*/
const auto& operator co_await() const & {
return static_cast<const detail::task::task_base<void>&>(*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 On resumption, returns a reference to the contained result.
*/
auto&& operator co_await() && {
return static_cast<detail::task::task_base<void>&&>(*this);
}
};
#endif /* _DOXYGEN_ */
namespace detail::task {
/**
* @brief Awaitable returned from task::promise_t's final_suspend. Resumes the parent and cleans up its handle if needed
*/
template <typename R>
struct final_awaiter {
/**
* @brief Always suspend at the end of the task. This allows us to clean up and resume the parent
*/
[[nodiscard]] bool await_ready() const 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
* @return std::coroutine_handle<> Handle to resume, which is either the parent if present or std::noop_coroutine() otherwise
*/
[[nodiscard]] std_coroutine::coroutine_handle<> await_suspend(handle_t<R> 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() const noexcept {}
};
/**
* @brief Base implementation of task::promise_t, without the logic that would depend on the return type. Meant to be inherited from
*/
struct promise_base {
/**
* @brief State of the task, used to keep track of lifetime and status
*/
std::atomic<state_t> state = state_t::started;
/**
* @brief Whether the task is cancelled or not.
*/
std::atomic<bool> cancelled = false;
/**
* @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 <a href="https://en.cppreference.com/w/cpp/error/exception_ptr>std::exception_ptr</a>
*/
std::exception_ptr exception = nullptr;
#ifdef DPP_CORO_TEST
promise_base() {
++coro_alloc_count<promise_base>;
}
~promise_base() {
--coro_alloc_count<promise_base>;
}
#endif
/**
* @brief Function called by the standard library when the coroutine is created.
*
* @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_never">std::suspend_never</a> Don't suspend, the coroutine starts immediately.
*/
[[nodiscard]] 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 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;
}
}
/**
* @brief Proxy awaitable that wraps any co_await inside the task and checks for cancellation on resumption
*
* @see await_transform
*/
template <typename A>
struct proxy_awaiter {
/** @brief The promise_t object bound to this proxy */
const task::promise_base &promise;
/** @brief The inner awaitable being awaited */
A awaitable;
/** @brief Wrapper for the awaitable's await_ready */
[[nodiscard]] bool await_ready() noexcept(noexcept(awaitable.await_ready())) {
return awaitable.await_ready();
}
/** @brief Wrapper for the awaitable's await_suspend */
template <typename T>
[[nodiscard]] decltype(auto) await_suspend(T&& handle) noexcept(noexcept(awaitable.await_suspend(std::forward<T>(handle)))) {
return awaitable.await_suspend(std::forward<T>(handle));
}
/**
* @brief Wrapper for the awaitable's await_resume, throws if the task is cancelled
*
* @throw dpp::task_cancelled_exception If the task was cancelled
*/
decltype(auto) await_resume() {
if (promise.cancelled.load()) {
throw dpp::task_cancelled_exception{"task was cancelled"};
}
return awaitable.await_resume();
}
};
/**
* @brief Function called whenever co_await is used inside of the task
*
* @throw dpp::task_cancelled_exception On resumption if the task was cancelled
*
* @return @ref proxy_awaiter Returns a proxy awaiter that will check for cancellation on resumption
*/
template <typename T>
[[nodiscard]] auto await_transform(T&& expr) const noexcept(noexcept(co_await_resolve(std::forward<T>(expr)))) {
using awaitable_t = decltype(co_await_resolve(std::forward<T>(expr)));
return proxy_awaiter<awaitable_t>{*this, co_await_resolve(std::forward<T>(expr))};
}
};
/**
* @brief Implementation of task::promise_t for non-void return type
*/
template <typename R>
struct promise_t : promise_base {
/**
* @brief Destructor. Destroys the value if it was constructed.
*/
~promise_t() {
if (state.load() == state_t::done && !exception) {
std::destroy_at(reinterpret_cast<R *>(result_storage.data()));
}
}
/**
* @brief Stored return value of the coroutine.
*
*/
alignas(R) std::array<std::byte, sizeof(R)> result_storage;
/**
* @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<R>) requires std::move_constructible<R> {
std::construct_at<R>(reinterpret_cast<R *>(result_storage.data()), static_cast<R&&>(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<R>) requires std::copy_constructible<R> {
std::construct_at<R>(reinterpret_cast<R *>(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 <typename T>
requires (!std::is_same_v<R, std::remove_cvref_t<T>> && std::convertible_to<T, R>)
void return_value(T&& expr) noexcept (std::is_nothrow_convertible_v<T, R>) {
std::construct_at<R>(reinterpret_cast<R *>(result_storage.data()), std::forward<T>(expr));
}
/**
* @brief Function called by the standard library when the coroutine is created.
*
* @return dpp::task The coroutine object
*/
[[nodiscard]] dpp::task<R> get_return_object() noexcept {
return dpp::task<R>{handle_t<R>::from_promise(*this)};
}
/**
* @brief Function called by the standard library when the coroutine reaches its last suspension point
*
* @return final_awaiter Special object containing the chain resolution and clean-up logic.
*/
[[nodiscard]] final_awaiter<R> final_suspend() const noexcept {
return {};
}
};
/**
* @brief Implementation of task::promise_t for void return type
*/
template <>
struct promise_t<void> : 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() const noexcept {}
/**
* @brief Function called by the standard library when the coroutine is created.
*
* @return task The coroutine object
*/
[[nodiscard]] dpp::task<void> get_return_object() noexcept {
return dpp::task<void>{handle_t<void>::from_promise(*this)};
}
/**
* @brief Function called by the standard library when the coroutine reaches its last suspension point
*
* @return final_awaiter Special object containing the chain resolution and clean-up logic.
*/
[[nodiscard]] final_awaiter<void> final_suspend() const noexcept {
return {};
}
};
template <typename R>
std_coroutine::coroutine_handle<> final_awaiter<R>::await_suspend(handle_t<R> handle) const noexcept {
promise_t<R> &promise = handle.promise();
state_t previous_state = promise.state.exchange(state_t::done);
switch (previous_state) {
case state_t::started: // started but never awaited, suspend
return std_coroutine::noop_coroutine();
case state_t::awaited: // co_await-ed, resume parent
return promise.parent;
case state_t::dangling: // task object is gone, free the handle
handle.destroy();
return std_coroutine::noop_coroutine();
case 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;
}
// TODO: replace with __builtin_unreachable when we confirm this never happens with normal usage
return std_coroutine::noop_coroutine();
}
} // namespace detail::task
#ifndef _DOXYGEN_
inline void task<void>::await_resume_impl() const {
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
}
#endif /* _DOXYGEN_ */
DPP_CHECK_ABI_COMPAT(task<void>, task_dummy)
DPP_CHECK_ABI_COMPAT(task<uint64_t>, task_dummy)
} // namespace dpp
/**
* @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise_t type from a coroutine function.
*/
template<typename T, typename... Args>
struct dpp::detail::std_coroutine::coroutine_traits<dpp::task<T>, Args...> {
using promise_type = dpp::detail::task::promise_t<T>;
};
#endif /* DPP_CORO */

View File

@@ -0,0 +1,532 @@
/************************************************************************************
*
* 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 "job.h"
#include <atomic>
#include <array>
#include <memory>
#include <limits>
#include <optional>
namespace dpp {
template <typename T>
class event_router_t;
namespace detail {
namespace event_router {
template <typename T>
class awaitable;
}
/**
* @brief Internal cogwheels for dpp::when_any
*/
namespace when_any {
/**
* @brief Current state of a when_any object
*/
enum class await_state {
/**
* @brief Object was started but not awaited
*/
started,
/**
* @brief Object is being awaited
*/
waiting,
/**
* @brief Object was resumed
*/
done,
/**
* @brief Object was destroyed
*/
dangling
};
/**
* @brief Type trait helper to obtain the actual type that will be used by a when_any when a type is passed as a parameter.
* May specialize for certain types for specific behavior, e.g. for an event_router, store the awaitable directly
*/
template <typename T>
struct arg_helper_s {
/** Raw type of the awaitable */
using type = T;
/** Helper static method to get the awaitable from a variable */
static decltype(auto) get(auto&& v) {
return static_cast<decltype(v)>(v);
}
};
template <typename T>
struct arg_helper_s<dpp::event_router_t<T>> {
using type = event_router::awaitable<T>;
template <typename U>
#ifndef _DOXYGEN
requires (std::same_as<std::remove_cvref_t<U>, dpp::event_router_t<T>>)
#endif
static event_router::awaitable<T> get(U&& v) {
return static_cast<U>(v).operator co_await();
}
};
/**
* @brief Alias for the actual type that an awaitable will be stored as in a when_any.
* For example if given an event_router, store the awaitable, not the event_router.
*/
template <typename T>
using awaitable_type = typename arg_helper_s<T>::type;
/**
* @brief Helper struct with a method to convert an awaitable parameter to the actual value it will be stored as.
* For example if given an event_router, store the awaitable, not the event_router.
*/
template <typename T>
using arg_helper = arg_helper_s<std::remove_cvref_t<T>>;
/**
* @brief Empty result from void-returning awaitable
*/
struct empty{};
/**
* @brief Actual type a result will be stores as in when_any
*/
template <typename T>
using storage_type = std::conditional_t<std::is_void_v<T>, empty, T>;
/**
* @brief Concept satisfied if a stored result is void
*/
template <typename T>
concept void_result = std::same_as<T, empty>;
}
} // namespace detail
/**
* @class when_any when_any.h coro/when_any.h
* @brief Experimental class to co_await on a bunch of awaitable objects, resuming when the first one completes.
* On completion, returns a @ref result object that contains the index of the awaitable that finished first.
* A user can call @ref result::index() and @ref result::get<N>() on the result object to get the result, similar to std::variant.
*
* @see when_any::result
* @tparam Args... Type of each awaitable to await on
*/
template <typename... Args>
#ifndef _DOXYGEN_
requires (sizeof...(Args) >= 1)
#endif
class when_any {
/**
* @brief Alias for the type of the result variant
*/
using variant_type = std::variant<std::exception_ptr, std::remove_cvref_t<detail::when_any::storage_type<detail::awaitable_result<Args>>>...>;
/**
* @brief Alias for the result type of the Nth arg.
*
* @tparam N index of the argument to fetch
*/
template <size_t N>
using result_t = std::variant_alternative_t<N + 1, variant_type>;
/**
* @brief State shared between all the jobs to spawn
*/
struct state_t {
/**
* @brief Constructor for the internal state. Its arguments are used to construct each awaitable
*/
template <typename... Args_>
state_t(Args_&&... args) : awaitables{std::forward<Args_>(args)...} {}
/**
* @brief Awaitable objects to handle.
*/
std::tuple<Args...> awaitables;
/**
* @brief Result or exception, as a variant. This will contain the result of the first awaitable to finish
*/
variant_type result{};
/**
* @brief Coroutine handle to resume after finishing an awaitable
*/
detail::std_coroutine::coroutine_handle<> handle{};
/**
* @brief Index of the awaitable that finished. Initialized to the maximum value of std::size_t.
*/
size_t index_finished = std::numeric_limits<std::size_t>::max();
/**
* @brief State of the when_any object.
*
* @see detail::when_any::await_state
*/
std::atomic<detail::when_any::await_state> owner_state{detail::when_any::await_state::started};
};
/**
* @brief Shared pointer to the state shared between the jobs spawned. Contains the awaitable objects and the result.
*/
std::shared_ptr<state_t> my_state{nullptr};
/**
* @brief Spawn a dpp::job handling the Nth argument.
*
* @tparam N Index of the argument to handle
* @return dpp::job Job handling the Nth argument
*/
template <size_t N>
static dpp::job make_job(std::shared_ptr<state_t> shared_state) {
/**
* Any exceptions from the awaitable's await_suspend should be thrown to the caller (the coroutine creating the when_any object)
* If the co_await passes, and it is the first one to complete, try construct the result, catch any exceptions to rethrow at resumption, and resume.
*/
if constexpr (!std::same_as<result_t<N>, detail::when_any::empty>) {
decltype(auto) result = co_await std::get<N>(shared_state->awaitables);
if (auto s = shared_state->owner_state.load(std::memory_order_relaxed); s == detail::when_any::await_state::dangling || s == detail::when_any::await_state::done) {
co_return;
}
using result_t = decltype(result);
/* Try construct, prefer move if possible, store any exception to rethrow */
try {
if constexpr (std::is_lvalue_reference_v<result_t> && !std::is_const_v<result_t> && std::is_move_constructible_v<std::remove_cvref_t<result_t>>) {
shared_state->result.template emplace<N + 1>(std::move(result));
} else {
shared_state->result.template emplace<N + 1>(result);
}
} catch (...) {
shared_state->result.template emplace<0>(std::current_exception());
}
} else {
co_await std::get<N>(shared_state->awaitables);
if (auto s = shared_state->owner_state.load(std::memory_order_relaxed); s == detail::when_any::await_state::dangling || s == detail::when_any::await_state::done) {
co_return;
}
shared_state->result.template emplace<N + 1>();
}
if (shared_state->owner_state.exchange(detail::when_any::await_state::done) != detail::when_any::await_state::waiting) {
co_return;
}
if (auto handle = shared_state->handle; handle) {
shared_state->index_finished = N;
shared_state->handle.resume();
}
}
/**
* @brief Spawn a dpp::job to handle each awaitable.
* Each of them will co_await the awaitable and set the result if they are the first to finish
*/
void make_jobs() {
[]<size_t... Ns>(when_any *self, std::index_sequence<Ns...>) {
(make_job<Ns>(self->my_state), ...);
}(this, std::index_sequence_for<Args...>{});
}
public:
/**
* @brief Object returned by \ref operator co_await() on resumption. Can be moved but not copied.
*/
class result {
friend class when_any<Args...>;
/**
* @brief Reference to the shared state to pull the data from
*/
std::shared_ptr<state_t> shared_state;
/**
* @brief Default construction is deleted
*/
result() = delete;
/**
* @brief Internal constructor taking the shared state
*/
result(std::shared_ptr<state_t> state) : shared_state{state} {}
public:
/**
* @brief Move constructor
*/
result(result&&) = default;
/**
* @brief This object is not copyable.
*/
result(const result &) = delete;
/**
* @brief Move assignment operator
*/
result &operator=(result&&) = default;
/**
* @brief This object is not copyable.
*/
result &operator=(const result&) = delete;
/**
* @brief Retrieve the index of the awaitable that finished first.
*
* @return size_t Index of the awaitable that finished first, relative to the template arguments of when_any
*/
size_t index() const noexcept {
return shared_state->index_finished;
}
/**
* @brief Retrieve the non-void result of an awaitable.
*
* @tparam N Index of the result to retrieve. Must correspond to index().
* @throw ??? Throws any exception triggered at construction, or std::bad_variant_access if N does not correspond to index()
* @return Result of the awaitable as a reference.
*/
template <size_t N>
#ifndef _DOXYGEN_
requires (!detail::when_any::void_result<result_t<N>>)
#endif
result_t<N>& get() & {
if (is_exception()) {
std::rethrow_exception(std::get<0>(shared_state->result));
}
return std::get<N + 1>(shared_state->result);
}
/**
* @brief Retrieve the non-void result of an awaitable.
*
* @tparam N Index of the result to retrieve. Must correspond to index().
* @throw ??? Throws any exception triggered at construction, or std::bad_variant_access if N does not correspond to index()
* @return Result of the awaitable as a cpnst reference.
*/
template <size_t N>
#ifndef _DOXYGEN_
requires (!detail::when_any::void_result<result_t<N>>)
#endif
const result_t<N>& get() const& {
if (is_exception()) {
std::rethrow_exception(std::get<0>(shared_state->result));
}
return std::get<N + 1>(shared_state->result);
}
/**
* @brief Retrieve the non-void result of an awaitable.
*
* @tparam N Index of the result to retrieve. Must correspond to index().
* @throw ??? Throws any exception triggered at construction, or std::bad_variant_access if N does not correspond to index()
* @return Result of the awaitable as an rvalue reference.
*/
template <size_t N>
#ifndef _DOXYGEN_
requires (!detail::when_any::void_result<result_t<N>>)
#endif
result_t<N>&& get() && {
if (is_exception()) {
std::rethrow_exception(std::get<0>(shared_state->result));
}
return std::get<N + 1>(shared_state->result);
}
/**
* @brief Cannot retrieve a void result.
*/
template <size_t N>
#ifndef _DOXYGEN
requires (detail::when_any::void_result<result_t<N>>)
#endif
[[deprecated("cannot retrieve a void result")]] void get() = delete;
/**
* @brief Checks whether the return of the first awaitable triggered an exception, that is, a call to get() will rethrow.
*
* @return Whether or not the result is an exception
*/
[[nodiscard]] bool is_exception() const noexcept {
return shared_state->result.index() == 0;
}
};
/**
* @brief Object returned by \ref operator co_await(). Meant to be used by the standard library, not by a user.
*
* @see result
*/
struct awaiter {
/**
* @brief Pointer to the when_any object
*/
when_any *self;
/**
* @brief First function called by the standard library when using co_await.
*
* @return bool Whether the result is ready
*/
[[nodiscard]] bool await_ready() const noexcept {
return self->await_ready();
}
/**
* @brief Second function called by the standard library when using co_await.
*
* @return bool Returns false if we want to resume immediately.
*/
bool await_suspend(detail::std_coroutine::coroutine_handle<> caller) noexcept {
auto sent = detail::when_any::await_state::started;
self->my_state->handle = caller;
return self->my_state->owner_state.compare_exchange_strong(sent, detail::when_any::await_state::waiting); // true (suspend) if `started` was replaced with `waiting` -- false (resume) if the value was not `started` (`done` is the only other option)
}
/**
* @brief Third and final function called by the standard library when using co_await. Returns the result object.
*
* @see result
*/
result await_resume() const noexcept {
return {self->my_state};
}
};
/**
* @brief Default constructor.
* A when_any object created this way holds no state
*/
when_any() = default;
/**
* @brief Constructor from awaitable objects. Each awaitable is executed immediately and the when_any object can then be co_await-ed later.
*
* @throw ??? Any exception thrown by the start of each awaitable will propagate to the caller.
* @param args Arguments to construct each awaitable from. The when_any object will construct an awaitable for each, it is recommended to pass rvalues or std::move.
*/
template <typename... Args_>
#ifndef _DOXYGEN_
requires (sizeof...(Args_) == sizeof...(Args))
#endif /* _DOXYGEN_ */
when_any(Args_&&... args) : my_state{std::make_shared<state_t>(detail::when_any::arg_helper<Args_>::get(std::forward<Args_>(args))...)} {
make_jobs();
}
/**
* @brief This object is not copyable.
*/
when_any(const when_any &) = delete;
/**
* @brief Move constructor.
*/
when_any(when_any &&) noexcept = default;
/**
* @brief On destruction the when_any will try to call @ref dpp::task::cancel() cancel() on each of its awaitable if they have such a method.
*
* @note If you are looking to use a custom type with when_any and want it to cancel on its destruction,
* make sure it has a cancel() method, which will trigger an await_resume() throwing a dpp::task_cancelled_exception.
* This object will swallow the exception and return cleanly. Any other exception will be thrown back to the resumer.
*/
~when_any() {
if (!my_state)
return;
my_state->owner_state = detail::when_any::await_state::dangling;
[]<size_t... Ns>(when_any *self, std::index_sequence<Ns...>) constexpr {
constexpr auto cancel = []<size_t N>(when_any *self) constexpr {
if constexpr (requires { std::get<N>(self->my_state->awaitables).cancel(); }) {
try {
std::get<N>(self->my_state->awaitables).cancel();
} catch (...) {
// swallow any exception. no choice here, we're in a destructor
}
}
};
(cancel.template operator()<Ns>(self), ...);
}(this, std::index_sequence_for<Args...>());
}
/**
* @brief This object is not copyable.
*/
when_any &operator=(const when_any &) = delete;
/**
* @brief Move assignment operator.
*/
when_any &operator=(when_any &&) noexcept = default;
/**
* @brief Check whether a call to co_await would suspend.
*
* @note This can change from false to true at any point, but not the other way around.
* @return bool Whether co_await would suspend
*/
[[nodiscard]] bool await_ready() const noexcept {
return my_state->owner_state == detail::when_any::await_state::done;
}
/**
* @brief Suspend the caller until any of the awaitables completes.
*
* @see result
* @throw ??? On resumption, throws any exception caused by the construction of the result.
* @return result On resumption, this object returns an object that allows to retrieve the index and result of the awaitable.
*/
[[nodiscard]] awaiter operator co_await() noexcept {
return {this};
}
};
template <typename... Args>
#ifndef _DOXYGEN_
requires (sizeof...(Args) >= 1)
#endif /* _DOXYGEN_ */
when_any(Args...) -> when_any<detail::when_any::awaitable_type<Args>...>;
} /* namespace dpp */
#endif