add basic discord support
This commit is contained in:
189
DPP/include/dpp/coro/async.h
Normal file
189
DPP/include/dpp/coro/async.h
Normal file
@@ -0,0 +1,189 @@
|
||||
/************************************************************************************
|
||||
*
|
||||
* 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>
|
||||
|
||||
#include <dpp/coro/awaitable.h>
|
||||
|
||||
namespace dpp {
|
||||
|
||||
struct async_dummy : awaitable_dummy {
|
||||
std::shared_ptr<int> dummy_shared_state = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#ifdef DPP_CORO
|
||||
|
||||
#include "coro.h"
|
||||
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
namespace dpp {
|
||||
|
||||
namespace detail {
|
||||
|
||||
namespace async {
|
||||
|
||||
/**
|
||||
* @brief Shared state of the async and its callback, to be used across threads.
|
||||
*/
|
||||
template <typename R>
|
||||
struct callback {
|
||||
/**
|
||||
* @brief Promise object to set the result into
|
||||
*/
|
||||
std::shared_ptr<basic_promise<R>> promise{nullptr};
|
||||
|
||||
/**
|
||||
* @brief Call operator, sets the value in the promise and notifies any awaiter
|
||||
*
|
||||
* @param v Callback value
|
||||
*/
|
||||
template <typename U = R>
|
||||
void operator()(const U& v) const requires (std::convertible_to<const U&, R>) {
|
||||
promise->set_value(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Call operator, sets the value in the promise and notifies any awaiter
|
||||
*
|
||||
* @param v Callback value
|
||||
*/
|
||||
template <typename U = R>
|
||||
void operator()(U&& v) const requires (std::convertible_to<U&&, R>) {
|
||||
promise->set_value(std::move(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Call operator, sets the value in the promise and notifies any awaiter
|
||||
*/
|
||||
void operator()() const requires (std::is_void_v<R>)
|
||||
{
|
||||
promise->set_value();
|
||||
}
|
||||
};
|
||||
|
||||
} // 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 : public awaitable<R> {
|
||||
/**
|
||||
* @brief Callable object to pass to API calls
|
||||
*/
|
||||
detail::async::callback<R> api_callback{};
|
||||
|
||||
/**
|
||||
* @brief Internal promise constructor, grabs a promise object for the callback to use
|
||||
*/
|
||||
explicit async(std::shared_ptr<basic_promise<R>> &&promise) : awaitable<R>{promise.get()}, api_callback{std::move(promise)} {}
|
||||
|
||||
public:
|
||||
using awaitable<R>::awaitable; // use awaitable's constructors
|
||||
using awaitable<R>::operator=; // use async_base's assignment operator
|
||||
using awaitable<R>::await_ready; // expose await_ready as public
|
||||
|
||||
/**
|
||||
* @brief The return type of the API call. Defaults to confirmation_callback_t
|
||||
*/
|
||||
using result_type = R;
|
||||
|
||||
/**
|
||||
* @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) : async{std::make_shared<basic_promise<R>>()} {
|
||||
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(Fun &&fun, Args&&... args) : async{std::make_shared<basic_promise<R>>()} {
|
||||
std::invoke(std::forward<Fun>(fun), std::forward<Args>(args)..., api_callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is disabled.
|
||||
*/
|
||||
async(const async&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor, moves the awaitable async object
|
||||
*/
|
||||
async(async&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator is disabled.
|
||||
*/
|
||||
async& operator=(const async&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator, moves the awaitable async object
|
||||
*/
|
||||
async& operator=(async&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor, signals to the callback that the async object is gone and shouldn't be notified of the result
|
||||
*/
|
||||
~async() {
|
||||
this->abandon();
|
||||
}
|
||||
};
|
||||
|
||||
DPP_CHECK_ABI_COMPAT(async<>, async_dummy);
|
||||
|
||||
}
|
||||
|
||||
#endif /* DPP_CORO */
|
||||
733
DPP/include/dpp/coro/awaitable.h
Normal file
733
DPP/include/dpp/coro/awaitable.h
Normal file
@@ -0,0 +1,733 @@
|
||||
/************************************************************************************
|
||||
*
|
||||
* 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 <iostream>
|
||||
|
||||
#include <dpp/utility.h>
|
||||
|
||||
namespace dpp {
|
||||
|
||||
struct awaitable_dummy {
|
||||
int *promise_dummy = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#ifdef DPP_CORO
|
||||
|
||||
#include <dpp/coro/coro.h>
|
||||
|
||||
// Do not include <coroutine> as coro.h includes <experimental/coroutine> or <coroutine> depending on clang version
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <exception>
|
||||
#include <atomic>
|
||||
|
||||
namespace dpp {
|
||||
|
||||
namespace detail::promise {
|
||||
|
||||
/**
|
||||
* @brief State of a promise
|
||||
*/
|
||||
enum state_flags {
|
||||
/**
|
||||
* @brief Promise is empty
|
||||
*/
|
||||
sf_none = 0b0000000,
|
||||
|
||||
/**
|
||||
* @brief Promise has spawned an awaitable
|
||||
*/
|
||||
sf_has_awaitable = 0b00000001,
|
||||
|
||||
/**
|
||||
* @brief Promise is being awaited
|
||||
*/
|
||||
sf_awaited = 0b00000010,
|
||||
|
||||
/**
|
||||
* @brief Promise has a result
|
||||
*/
|
||||
sf_ready = 0b00000100,
|
||||
|
||||
/**
|
||||
* @brief Promise has completed, no more results are expected
|
||||
*/
|
||||
sf_done = 0b00001000,
|
||||
|
||||
/**
|
||||
* @brief Promise was broken - future or promise is gone
|
||||
*/
|
||||
sf_broken = 0b0010000
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class promise_base;
|
||||
|
||||
/**
|
||||
* @brief Empty result from void-returning awaitable
|
||||
*/
|
||||
struct empty{};
|
||||
|
||||
/**
|
||||
* @brief Variant for the 3 conceptual values of a coroutine:
|
||||
*/
|
||||
template <typename T>
|
||||
using result_t = std::variant<std::monostate, std::conditional_t<std::is_void_v<T>, empty, T>, std::exception_ptr>;
|
||||
|
||||
template <typename T>
|
||||
void spawn_sync_wait_job(auto* awaitable, std::condition_variable &cv, auto&& result);
|
||||
|
||||
} /* namespace detail::promise */
|
||||
|
||||
template <typename Derived>
|
||||
class basic_awaitable {
|
||||
protected:
|
||||
/**
|
||||
* @brief Implementation for sync_wait. This is code used by sync_wait, sync_wait_for, sync_wait_until.
|
||||
*
|
||||
* @tparam Timed Whether the wait function times out or not
|
||||
* @param do_wait Function to do the actual wait on the cv
|
||||
* @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out.
|
||||
* @return If T is non-void, returns a std::optional<T> for which an absence of value means timed out.
|
||||
*/
|
||||
template <bool Timed>
|
||||
auto sync_wait_impl(auto&& do_wait) {
|
||||
using result_type = decltype(detail::co_await_resolve(std::declval<Derived>()).await_resume());
|
||||
using variant_type = detail::promise::result_t<result_type>;
|
||||
variant_type result;
|
||||
std::condition_variable cv;
|
||||
|
||||
detail::promise::spawn_sync_wait_job<result_type>(static_cast<Derived*>(this), cv, result);
|
||||
do_wait(cv, result);
|
||||
/*
|
||||
* Note: we use .index() here to support dpp::promise<std::exception_ptr> & dpp::promise<std::monostate> :D
|
||||
*/
|
||||
if (result.index() == 2) {
|
||||
std::rethrow_exception(std::get<2>(result));
|
||||
}
|
||||
if constexpr (!Timed) { // no timeout
|
||||
if constexpr (!std::is_void_v<result_type>) {
|
||||
return std::get<1>(result);
|
||||
}
|
||||
} else { // timeout
|
||||
if constexpr (std::is_void_v<result_type>) {
|
||||
return result.index() == 1 ? true : false;
|
||||
} else {
|
||||
return result.index() == 1 ? std::optional<result_type>{std::get<1>(result)} : std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Blocks this thread and waits for the awaitable to finish.
|
||||
*
|
||||
* @attention This will BLOCK THE THREAD. It is likely you want to use co_await instead.
|
||||
* @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out.
|
||||
* @return If T is non-void, returns a std::optional<T> for which an absence of value means timed out.
|
||||
*/
|
||||
auto sync_wait() {
|
||||
return sync_wait_impl<false>([](std::condition_variable &cv, auto&& result) {
|
||||
std::mutex m{};
|
||||
std::unique_lock lock{m};
|
||||
cv.wait(lock, [&result] { return result.index() != 0; });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Blocks this thread and waits for the awaitable to finish.
|
||||
*
|
||||
* @attention This will BLOCK THE THREAD. It is likely you want to use co_await instead.
|
||||
* @param duration Maximum duration to wait for
|
||||
* @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out.
|
||||
* @return If T is non-void, returns a std::optional<T> for which an absence of value means timed out.
|
||||
*/
|
||||
template <class Rep, class Period>
|
||||
auto sync_wait_for(const std::chrono::duration<Rep, Period>& duration) {
|
||||
return sync_wait_impl<true>([duration](std::condition_variable &cv, auto&& result) {
|
||||
std::mutex m{};
|
||||
std::unique_lock lock{m};
|
||||
cv.wait_for(lock, duration, [&result] { return result.index() != 0; });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Blocks this thread and waits for the awaitable to finish.
|
||||
*
|
||||
* @attention This will BLOCK THE THREAD. It is likely you want to use co_await instead.
|
||||
* @param time Maximum time point to wait for
|
||||
* @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out.
|
||||
* @return If T is non-void, returns a std::optional<T> for which an absence of value means timed out.
|
||||
*/
|
||||
template <class Clock, class Duration>
|
||||
auto sync_wait_until(const std::chrono::time_point<Clock, Duration> &time) {
|
||||
return sync_wait_impl<true>([time](std::condition_variable &cv, auto&& result) {
|
||||
std::mutex m{};
|
||||
std::unique_lock lock{m};
|
||||
cv.wait_until(lock, time, [&result] { return result.index() != 0; });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Generic awaitable class, represents a future value that can be co_await-ed on.
|
||||
*
|
||||
* Roughly equivalent of std::future for coroutines, with the crucial distinction that the future does not own a reference to a "shared state".
|
||||
* It holds a non-owning reference to the promise, which must be kept alive for the entire lifetime of the awaitable.
|
||||
*
|
||||
* @tparam T Type of the asynchronous value
|
||||
* @see promise
|
||||
*/
|
||||
template <typename T>
|
||||
class awaitable : public basic_awaitable<awaitable<T>> {
|
||||
protected:
|
||||
friend class detail::promise::promise_base<T>;
|
||||
|
||||
using shared_state = detail::promise::promise_base<T>;
|
||||
using state_flags = detail::promise::state_flags;
|
||||
|
||||
/**
|
||||
* @brief The type of the result produced by this task.
|
||||
*/
|
||||
using result_type = T;
|
||||
|
||||
/**
|
||||
* @brief Non-owning pointer to the promise, which must be kept alive for the entire lifetime of the awaitable.
|
||||
*/
|
||||
shared_state *state_ptr = nullptr;
|
||||
|
||||
/**
|
||||
* @brief Construct from a promise.
|
||||
*
|
||||
* @param promise The promise to refer to.
|
||||
*/
|
||||
awaitable(shared_state *promise) noexcept : state_ptr{promise} {}
|
||||
|
||||
/**
|
||||
* @brief Abandons the promise.
|
||||
*
|
||||
* Set the promise's state to broken and unlinks this awaitable.
|
||||
*
|
||||
* @return uint8_t Flags previously held before setting them to broken
|
||||
*/
|
||||
uint8_t abandon();
|
||||
/**
|
||||
* @brief Awaiter returned by co_await.
|
||||
*
|
||||
* Contains the await_ready, await_suspend and await_resume functions required by the C++ standard.
|
||||
* This class is CRTP-like, in that it will refer to an object derived from awaitable.
|
||||
*
|
||||
* @tparam Derived Type of reference to refer to the awaitable.
|
||||
*/
|
||||
template <typename Derived>
|
||||
struct awaiter {
|
||||
Derived awaitable_obj;
|
||||
|
||||
/**
|
||||
* @brief First function called by the standard library when co_await-ing this object.
|
||||
*
|
||||
* @throws dpp::logic_exception If the awaitable's valid() would return false.
|
||||
* @return bool Whether the result is ready, in which case we don't need to suspend
|
||||
*/
|
||||
bool await_ready() const;
|
||||
|
||||
/**
|
||||
* @brief Second function called by the standard library when co_await-ing this object.
|
||||
*
|
||||
* @throws dpp::logic_exception If the awaitable's valid() would return false.
|
||||
* At this point the coroutine frame was allocated and suspended.
|
||||
*
|
||||
* @return bool Whether we do need to suspend or not
|
||||
*/
|
||||
bool await_suspend(detail::std_coroutine::coroutine_handle<> handle);
|
||||
|
||||
/**
|
||||
* @brief Third and final function called by the standard library when co_await-ing this object, after resuming.
|
||||
*
|
||||
* @throw ? Any exception that occured during the retrieval of the value will be thrown
|
||||
* @return T The result.
|
||||
*/
|
||||
T await_resume();
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct an empty awaitable.
|
||||
*
|
||||
* Such an awaitable must be assigned a promise before it can be awaited.
|
||||
*/
|
||||
awaitable() = default;
|
||||
|
||||
/**
|
||||
* @brief Copy construction is disabled.
|
||||
*/
|
||||
awaitable(const awaitable&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move from another awaitable.
|
||||
*
|
||||
* @param rhs The awaitable to move from, left in an unspecified state after this.
|
||||
*/
|
||||
awaitable(awaitable&& rhs) noexcept : state_ptr(std::exchange(rhs.state_ptr, nullptr)) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Title :)
|
||||
*
|
||||
* We use this in the destructor
|
||||
*/
|
||||
void if_this_causes_an_invalid_read_your_promise_was_destroyed_before_your_awaitable____check_your_promise_lifetime() {
|
||||
abandon();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor.
|
||||
*
|
||||
* May signal to the promise that it was destroyed.
|
||||
*/
|
||||
~awaitable();
|
||||
|
||||
/**
|
||||
* @brief Copy assignment is disabled.
|
||||
*/
|
||||
awaitable& operator=(const awaitable&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move from another awaitable.
|
||||
*
|
||||
* @param rhs The awaitable to move from, left in an unspecified state after this.
|
||||
* @return *this
|
||||
*/
|
||||
awaitable& operator=(awaitable&& rhs) noexcept {
|
||||
abandon();
|
||||
state_ptr = std::exchange(rhs.state_ptr, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether this awaitable refers to a valid promise.
|
||||
*
|
||||
* @return bool Whether this awaitable refers to a valid promise or not
|
||||
*/
|
||||
bool valid() const noexcept;
|
||||
|
||||
/**
|
||||
* @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 or not
|
||||
*/
|
||||
bool await_ready() const;
|
||||
|
||||
/**
|
||||
* @brief Overload of the co_await operator.
|
||||
*
|
||||
* @return Returns an @ref awaiter referencing this awaitable.
|
||||
*/
|
||||
template <typename Derived>
|
||||
requires (std::is_base_of_v<awaitable, std::remove_cv_t<Derived>>)
|
||||
friend awaiter<Derived&> operator co_await(Derived& obj) noexcept {
|
||||
return {obj};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Overload of the co_await operator. Returns an @ref awaiter referencing this awaitable.
|
||||
*
|
||||
* @return Returns an @ref awaiter referencing this awaitable.
|
||||
*/
|
||||
template <typename Derived>
|
||||
requires (std::is_base_of_v<awaitable, std::remove_cv_t<Derived>>)
|
||||
friend awaiter<Derived&&> operator co_await(Derived&& obj) noexcept {
|
||||
return {std::move(obj)};
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail::promise {
|
||||
|
||||
/**
|
||||
* @brief Base class defining logic common to all promise types, aka the "write" end of an awaitable.
|
||||
*/
|
||||
template <typename T>
|
||||
class promise_base {
|
||||
protected:
|
||||
friend class awaitable<T>;
|
||||
|
||||
/**
|
||||
* @brief Variant representing one of either 3 states of the result value : empty, result, exception.
|
||||
*/
|
||||
using storage_type = result_t<T>;
|
||||
|
||||
/**
|
||||
* @brief State of the result value.
|
||||
*
|
||||
* @see storage_type
|
||||
*
|
||||
* @note use .index() instead of std::holds_alternative to support promise_base<std::exception_ptr> and promise_base<std::monostate> :)
|
||||
*/
|
||||
storage_type value = std::monostate{};
|
||||
|
||||
/**
|
||||
* @brief State of the awaitable tied to this promise.
|
||||
*/
|
||||
std::atomic<uint8_t> state = sf_none;
|
||||
|
||||
/**
|
||||
* @brief Coroutine handle currently awaiting the completion of this promise.
|
||||
*/
|
||||
std_coroutine::coroutine_handle<> awaiter = nullptr;
|
||||
|
||||
/**
|
||||
* @brief Check if the result is empty, throws otherwise.
|
||||
*
|
||||
* @throw dpp::logic_exception if the result isn't empty.
|
||||
*/
|
||||
void throw_if_not_empty() {
|
||||
if (value.index() != 0) [[unlikely]] {
|
||||
throw dpp::logic_exception("cannot set a value on a promise that already has one");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unlinks this promise from its currently linked awaiter and returns it.
|
||||
*
|
||||
* At the time of writing this is only used in the case of a serious internal error in dpp::task.
|
||||
* Avoid using this as this will crash if the promise is used after this.
|
||||
*/
|
||||
std_coroutine::coroutine_handle<> release_awaiter() {
|
||||
return std::exchange(awaiter, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new promise, with empty result.
|
||||
*/
|
||||
promise_base() = default;
|
||||
|
||||
/**
|
||||
* @brief Copy construction is disabled.
|
||||
*/
|
||||
promise_base(const promise_base&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move construction is disabled.
|
||||
*
|
||||
* awaitable hold a pointer to this object so moving is not possible.
|
||||
*/
|
||||
promise_base(promise_base&& rhs) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Copy assignment is disabled.
|
||||
*/
|
||||
promise_base &operator=(const promise_base&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment is disabled.
|
||||
*/
|
||||
promise_base &operator=(promise_base&& rhs) = delete;
|
||||
|
||||
/**
|
||||
* @brief Set this promise to an exception and resume any awaiter.
|
||||
*
|
||||
* @tparam Notify Whether to resume any awaiter or not.
|
||||
* @throws dpp::logic_exception if the promise is not empty.
|
||||
* @throws ? Any exception thrown by the coroutine if resumed will propagate
|
||||
*/
|
||||
template <bool Notify = true>
|
||||
void set_exception(std::exception_ptr ptr) {
|
||||
throw_if_not_empty();
|
||||
value.template emplace<2>(std::move(ptr));
|
||||
[[maybe_unused]] auto previous_value = this->state.fetch_or(sf_ready, std::memory_order::acq_rel);
|
||||
if constexpr (Notify) {
|
||||
if ((previous_value & sf_awaited) != 0) {
|
||||
this->awaiter.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Notify a currently awaiting coroutine that the result is ready.
|
||||
*
|
||||
* @note This may resume the coroutine on the current thread.
|
||||
* @throws ? Any exception thrown by the coroutine if resumed will propagate
|
||||
*/
|
||||
void notify_awaiter() {
|
||||
if ((state.load(std::memory_order::acquire) & sf_awaited) != 0) {
|
||||
awaiter.resume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get an awaitable object for this promise.
|
||||
*
|
||||
* @throws dpp::logic_exception if get_awaitable has already been called on this object.
|
||||
* @return awaitable<T> An object that can be co_await-ed to retrieve the value of this promise.
|
||||
*/
|
||||
awaitable<T> get_awaitable() {
|
||||
uint8_t previous_flags = state.fetch_or(sf_has_awaitable, std::memory_order::relaxed);
|
||||
if (previous_flags & sf_has_awaitable) [[unlikely]] {
|
||||
throw dpp::logic_exception{"an awaitable was already created from this promise"};
|
||||
}
|
||||
return {this};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generic promise class, represents the owning potion of an asynchronous value.
|
||||
*
|
||||
* This class is roughly equivalent to std::promise, with the crucial distinction that the promise *IS* the shared state.
|
||||
* As such, the promise needs to be kept alive for the entire time a value can be retrieved.
|
||||
*
|
||||
* @tparam T Type of the asynchronous value
|
||||
* @see awaitable
|
||||
*/
|
||||
template <typename T>
|
||||
class basic_promise : public detail::promise::promise_base<T> {
|
||||
public:
|
||||
using detail::promise::promise_base<T>::promise_base;
|
||||
using detail::promise::promise_base<T>::operator=;
|
||||
|
||||
/**
|
||||
* @brief Construct the result in place by forwarding the arguments, and by default resume any awaiter.
|
||||
*
|
||||
* @tparam Notify Whether to resume any awaiter or not.
|
||||
* @throws dpp::logic_exception if the promise is not empty.
|
||||
*/
|
||||
template <bool Notify = true, typename... Args>
|
||||
requires (std::constructible_from<T, Args...>)
|
||||
void emplace_value(Args&&... args) {
|
||||
this->throw_if_not_empty();
|
||||
try {
|
||||
this->value.template emplace<1>(std::forward<Args>(args)...);
|
||||
} catch (...) {
|
||||
this->value.template emplace<2>(std::current_exception());
|
||||
}
|
||||
[[maybe_unused]] auto previous_value = this->state.fetch_or(detail::promise::sf_ready, std::memory_order::acq_rel);
|
||||
if constexpr (Notify) {
|
||||
if (previous_value & detail::promise::sf_awaited) {
|
||||
this->awaiter.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct the result by forwarding reference, and resume any awaiter.
|
||||
*
|
||||
* @tparam Notify Whether to resume any awaiter or not.
|
||||
* @throws dpp::logic_exception if the promise is not empty.
|
||||
*/
|
||||
template <bool Notify = true, typename U = T>
|
||||
requires (std::convertible_to<U&&, T>)
|
||||
void set_value(U&& v) {
|
||||
emplace_value<Notify>(std::forward<U>(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a void result, and resume any awaiter.
|
||||
*
|
||||
* @tparam Notify Whether to resume any awaiter or not.
|
||||
* @throws dpp::logic_exception if the promise is not empty.
|
||||
*/
|
||||
template <bool Notify = true>
|
||||
requires (std::is_void_v<T>)
|
||||
void set_value() {
|
||||
this->throw_if_not_empty();
|
||||
this->value.template emplace<1>();
|
||||
[[maybe_unused]] auto previous_value = this->state.fetch_or(detail::promise::sf_ready, std::memory_order::acq_rel);
|
||||
if constexpr (Notify) {
|
||||
if (previous_value & detail::promise::sf_awaited) {
|
||||
this->awaiter.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Generic promise class, represents the owning potion of an asynchronous value.
|
||||
*
|
||||
* This class is roughly equivalent to std::promise, with the crucial distinction that the promise *IS* the shared state.
|
||||
* As such, the promise needs to be kept alive for the entire time a value can be retrieved.
|
||||
*
|
||||
* The difference between basic_promise and this object is that this one is moveable as it wraps an underlying basic_promise in a std::unique_ptr.
|
||||
*
|
||||
* @see awaitable
|
||||
*/
|
||||
template <typename T>
|
||||
class moveable_promise {
|
||||
/**
|
||||
* @brief Shared state, wrapped in a unique_ptr to allow move without disturbing an awaitable's promise pointer.
|
||||
*/
|
||||
std::unique_ptr<basic_promise<T>> shared_state = std::make_unique<basic_promise<T>>();
|
||||
|
||||
public:
|
||||
/**
|
||||
* @copydoc basic_promise<T>::emplace_value
|
||||
*/
|
||||
template <bool Notify = true, typename... Args>
|
||||
requires (std::constructible_from<T, Args...>)
|
||||
void emplace_value(Args&&... args) {
|
||||
shared_state->template emplace_value<Notify>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc basic_promise<T>::set_value(U&&)
|
||||
*/
|
||||
template <bool Notify = true, typename U = T>
|
||||
void set_value(U&& v) requires (std::convertible_to<U&&, T>) {
|
||||
shared_state->template set_value<Notify>(std::forward<U>(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc basic_promise<T>::set_value()
|
||||
*/
|
||||
template <bool Notify = true>
|
||||
void set_value() requires (std::is_void_v<T>) {
|
||||
shared_state->template set_value<Notify>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc basic_promise<T>::set_value(T&&)
|
||||
*/
|
||||
template <bool Notify = true>
|
||||
void set_exception(std::exception_ptr ptr) {
|
||||
shared_state->template set_exception<Notify>(std::move(ptr));
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc basic_promise<T>::notify_awaiter
|
||||
*/
|
||||
void notify_awaiter() {
|
||||
shared_state->notify_awaiter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc basic_promise<T>::get_awaitable
|
||||
*/
|
||||
awaitable<T> get_awaitable() {
|
||||
return shared_state->get_awaitable();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using promise = moveable_promise<T>;
|
||||
|
||||
template <typename T>
|
||||
auto awaitable<T>::abandon() -> uint8_t {
|
||||
uint8_t previous_state = state_flags::sf_broken;
|
||||
if (state_ptr) {
|
||||
previous_state = state_ptr->state.fetch_or(state_flags::sf_broken, std::memory_order::acq_rel);
|
||||
state_ptr = nullptr;
|
||||
}
|
||||
return previous_state;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
awaitable<T>::~awaitable() {
|
||||
if_this_causes_an_invalid_read_your_promise_was_destroyed_before_your_awaitable____check_your_promise_lifetime();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool awaitable<T>::valid() const noexcept {
|
||||
return state_ptr != nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool awaitable<T>::await_ready() const {
|
||||
if (!this->valid()) {
|
||||
throw dpp::logic_exception("cannot co_await an empty awaitable");
|
||||
}
|
||||
uint8_t state = this->state_ptr->state.load(std::memory_order::relaxed);
|
||||
return state & detail::promise::sf_ready;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename Derived>
|
||||
bool awaitable<T>::awaiter<Derived>::await_suspend(detail::std_coroutine::coroutine_handle<> handle) {
|
||||
auto &promise = *awaitable_obj.state_ptr;
|
||||
|
||||
promise.awaiter = handle;
|
||||
auto previous_flags = promise.state.fetch_or(detail::promise::sf_awaited, std::memory_order::relaxed);
|
||||
if (previous_flags & detail::promise::sf_awaited) {
|
||||
throw dpp::logic_exception("awaitable is already being awaited");
|
||||
}
|
||||
return !(previous_flags & detail::promise::sf_ready);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename Derived>
|
||||
T awaitable<T>::awaiter<Derived>::await_resume() {
|
||||
auto &promise = *awaitable_obj.state_ptr;
|
||||
|
||||
promise.state.fetch_and(~detail::promise::sf_awaited, std::memory_order::acq_rel);
|
||||
if (std::holds_alternative<std::exception_ptr>(promise.value)) {
|
||||
std::rethrow_exception(std::get<2>(promise.value));
|
||||
}
|
||||
if constexpr (!std::is_void_v<T>) {
|
||||
return std::get<1>(std::move(promise.value));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
template <typename Derived>
|
||||
bool awaitable<T>::awaiter<Derived>::await_ready() const {
|
||||
return static_cast<Derived>(awaitable_obj).await_ready();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#include <dpp/coro/job.h>
|
||||
|
||||
namespace dpp {
|
||||
|
||||
namespace detail::promise {
|
||||
|
||||
template <typename T>
|
||||
void spawn_sync_wait_job(auto* awaitable, std::condition_variable &cv, auto&& result) {
|
||||
[](auto* awaitable_, std::condition_variable &cv_, auto&& result_) -> dpp::job {
|
||||
try {
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
co_await *awaitable_;
|
||||
result_.template emplace<1>();
|
||||
} else {
|
||||
result_.template emplace<1>(co_await *awaitable_);
|
||||
}
|
||||
} catch (...) {
|
||||
result_.template emplace<2>(std::current_exception());
|
||||
}
|
||||
cv_.notify_all();
|
||||
}(awaitable, cv, std::forward<decltype(result)>(result));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* DPP_CORO */
|
||||
202
DPP/include/dpp/coro/coro.h
Normal file
202
DPP/include/dpp/coro/coro.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/************************************************************************************
|
||||
*
|
||||
* 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
|
||||
#ifdef DPP_CORO
|
||||
|
||||
#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>
|
||||
inline constexpr 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>
|
||||
inline constexpr 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>
|
||||
inline constexpr bool has_await_members;
|
||||
|
||||
/**
|
||||
* @brief Concept to check if a type can be used with co_await
|
||||
*
|
||||
* @note This is actually a C++20 concept but Doxygen doesn't do well with them
|
||||
*/
|
||||
template <typename T>
|
||||
inline constexpr bool awaitable_type;
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
||||
/**
|
||||
* @brief Concept to check if a type can be used with co_await
|
||||
*/
|
||||
template <typename T>
|
||||
concept awaitable_type = requires (T expr) { detail::co_await_resolve(expr).await_ready(); };
|
||||
|
||||
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 */
|
||||
|
||||
406
DPP/include/dpp/coro/coroutine.h
Normal file
406
DPP/include/dpp/coro/coroutine.h
Normal file
@@ -0,0 +1,406 @@
|
||||
/************************************************************************************
|
||||
*
|
||||
* 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 <dpp/coro/coro.h>
|
||||
#include <dpp/coro/awaitable.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>>;
|
||||
|
||||
} // 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 [[nodiscard("dpp::coroutine only starts when it is awaited, it will do nothing if discarded")]] coroutine : public basic_awaitable<coroutine<R>> {
|
||||
/**
|
||||
* @brief Promise has friend access for the constructor
|
||||
*/
|
||||
friend struct detail::coroutine::promise_t<R>;
|
||||
|
||||
/**
|
||||
* @brief Coroutine handle.
|
||||
*/
|
||||
detail::coroutine::handle_t<R> handle{nullptr};
|
||||
|
||||
/**
|
||||
* @brief Construct from a handle. Internal use only.
|
||||
*/
|
||||
coroutine(detail::coroutine::handle_t<R> h) : handle{h} {}
|
||||
|
||||
struct awaiter {
|
||||
/**
|
||||
* @brief Reference to the coroutine object being awaited.
|
||||
*/
|
||||
coroutine &coro;
|
||||
|
||||
/**
|
||||
* @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 (!coro.handle) {
|
||||
throw dpp::logic_exception("cannot co_await an empty coroutine");
|
||||
}
|
||||
return coro.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]] detail::coroutine::handle_t<R> await_suspend(detail::std_coroutine::coroutine_handle<T> caller) noexcept {
|
||||
coro.handle.promise().parent = caller;
|
||||
return coro.handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Final function called by the standard library when the coroutine is co_await-ed.
|
||||
*
|
||||
* Pops the coroutine's result and returns it.
|
||||
* @remark Do not call this manually, use the co_await keyword instead.
|
||||
*/
|
||||
R await_resume() {
|
||||
detail::coroutine::promise_t<R> &promise = coro.handle.promise();
|
||||
if (promise.exception) {
|
||||
std::rethrow_exception(promise.exception);
|
||||
}
|
||||
if constexpr (!std::is_void_v<R>) {
|
||||
return *std::exchange(promise.result, std::nullopt);
|
||||
} else {
|
||||
return; // unnecessary but makes lsp happy
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief The type of the result produced by this coroutine.
|
||||
*/
|
||||
using result_type = R;
|
||||
|
||||
/**
|
||||
* @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 : handle(std::exchange(other.handle, nullptr)) {}
|
||||
|
||||
/**
|
||||
* @brief Destructor, destroys the handle.
|
||||
*/
|
||||
~coroutine() {
|
||||
if (handle) {
|
||||
handle.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
handle = std::exchange(other.handle, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator co_await() {
|
||||
return awaiter{*this};
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
DPP_CHECK_ABI_COMPAT(coroutine<void>, coroutine_dummy)
|
||||
DPP_CHECK_ABI_COMPAT(coroutine<uint64_t>, coroutine_dummy)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @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/include/dpp/coro/job.h
Normal file
145
DPP/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 */
|
||||
446
DPP/include/dpp/coro/task.h
Normal file
446
DPP/include/dpp/coro/task.h
Normal file
@@ -0,0 +1,446 @@
|
||||
/************************************************************************************
|
||||
*
|
||||
* 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>
|
||||
#include <dpp/coro/awaitable.h>
|
||||
|
||||
namespace dpp {
|
||||
|
||||
struct task_dummy : awaitable_dummy {
|
||||
int* handle_dummy = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#ifdef DPP_CORO
|
||||
|
||||
#include <dpp/coro/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 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>>;
|
||||
|
||||
} // 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 [[nodiscard("dpp::task cancels itself on destruction. use co_await on it, or its sync_wait method")]] task : public awaitable<R> {
|
||||
friend struct detail::task::promise_t<R>;
|
||||
|
||||
using handle_t = detail::task::handle_t<R>;
|
||||
using state_flags = detail::promise::state_flags;
|
||||
|
||||
handle_t handle{};
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Construct from a coroutine handle. Internal use only
|
||||
*/
|
||||
explicit task(handle_t handle_) : awaitable<R>(&handle_.promise()), handle(handle_) {}
|
||||
|
||||
/**
|
||||
* @brief Clean up our handle, cancelling any running task
|
||||
*/
|
||||
void cleanup() {
|
||||
if (handle && this->valid()) {
|
||||
if (this->abandon() & state_flags::sf_done) {
|
||||
handle.destroy();
|
||||
} else {
|
||||
cancel();
|
||||
}
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor, creates a task not bound to a coroutine.
|
||||
*/
|
||||
task() = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is disabled
|
||||
*/
|
||||
task(const task &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor, grabs another task's coroutine handle
|
||||
*
|
||||
* @param other Task to move the handle from
|
||||
*/
|
||||
task(task &&other) noexcept : awaitable<R>(std::move(other)), handle(std::exchange(other.handle, nullptr)) {}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
cleanup();
|
||||
handle = std::exchange(other.handle, nullptr);
|
||||
awaitable<R>::operator=(std::move(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor.
|
||||
*
|
||||
* Destroys the handle. If the task is still running, it will be cancelled.
|
||||
*/
|
||||
~task() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 && (!this->valid() || handle.promise().state.load(std::memory_order_relaxed) == state_flags::sf_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
|
||||
*/
|
||||
task& cancel() & noexcept {
|
||||
handle.promise().cancelled.exchange(true, std::memory_order_relaxed);
|
||||
return *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
|
||||
*/
|
||||
task&& cancel() && noexcept {
|
||||
handle.promise().cancelled.exchange(true, std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
*/
|
||||
template <typename R>
|
||||
struct promise_base : basic_promise<R> {
|
||||
/**
|
||||
* @brief Whether the task is cancelled or not.
|
||||
*/
|
||||
std::atomic<bool> cancelled = false;
|
||||
|
||||
#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() {
|
||||
if ((this->state.load() & promise::state_flags::sf_broken) && !cancelled) {
|
||||
throw;
|
||||
}
|
||||
this->template set_exception<false>(std::current_exception());
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 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 <awaitable_type 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<R> {
|
||||
friend struct final_awaiter<R>;
|
||||
|
||||
/**
|
||||
* @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> {
|
||||
this->template set_value<false>(std::move(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> {
|
||||
this->template set_value<false>(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>) {
|
||||
this->template emplace_value<false>(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<void> {
|
||||
friend struct final_awaiter<void>;
|
||||
|
||||
/**
|
||||
* @brief Function called by the standard library when the coroutine co_returns
|
||||
*
|
||||
* Sets the promise state to finished.
|
||||
*/
|
||||
void return_void() noexcept {
|
||||
set_value<false>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
using state_flags = promise::state_flags;
|
||||
promise_t<R> &promise = handle.promise();
|
||||
uint8_t previous_state = promise.state.fetch_or(state_flags::sf_done);
|
||||
|
||||
if ((previous_state & state_flags::sf_awaited) != 0) { // co_await-ed, resume parent
|
||||
if ((previous_state & state_flags::sf_broken) != 0) { // major bug, these should never be set together
|
||||
// we don't have a cluster so just log it on cerr
|
||||
std::cerr << "dpp: task promise ended in both an awaited and dangling state. this is a bug and a memory leak, please report it to us!" << std::endl;
|
||||
}
|
||||
return promise.release_awaiter();
|
||||
}
|
||||
if ((previous_state & state_flags::sf_broken) != 0) { // task object is gone, free the handle
|
||||
handle.destroy();
|
||||
}
|
||||
return std_coroutine::noop_coroutine();
|
||||
}
|
||||
|
||||
} // namespace detail::task
|
||||
|
||||
DPP_CHECK_ABI_COMPAT(task<void>, task_dummy)
|
||||
DPP_CHECK_ABI_COMPAT(task<uint64_t>, task_dummy)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @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/include/dpp/coro/when_any.h
Normal file
532
DPP/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