mirror of
https://github.com/HappyTanuki/BumbleCee.git
synced 2025-12-18 13:13:28 +00:00
스킵 구현
This commit is contained in:
523
DPP-master/include/dpp/coro/async.h
Normal file
523
DPP-master/include/dpp/coro/async.h
Normal 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 */
|
||||
186
DPP-master/include/dpp/coro/coro.h
Normal file
186
DPP-master/include/dpp/coro/coro.h
Normal 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 */
|
||||
|
||||
589
DPP-master/include/dpp/coro/coroutine.h
Normal file
589
DPP-master/include/dpp/coro/coroutine.h
Normal 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 */
|
||||
145
DPP-master/include/dpp/coro/job.h
Normal file
145
DPP-master/include/dpp/coro/job.h
Normal 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 */
|
||||
784
DPP-master/include/dpp/coro/task.h
Normal file
784
DPP-master/include/dpp/coro/task.h
Normal 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 */
|
||||
532
DPP-master/include/dpp/coro/when_any.h
Normal file
532
DPP-master/include/dpp/coro/when_any.h
Normal 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
|
||||
Reference in New Issue
Block a user