Files
BumbleCee/DPP-master/include/dpp/coro/coroutine.h
2023-12-20 19:45:25 +09:00

590 lines
18 KiB
C++

/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2022 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
************************************************************************************/
#pragma once
#include <dpp/utility.h>
namespace dpp {
struct coroutine_dummy {
int *handle_dummy = nullptr;
};
}
#ifdef DPP_CORO
#include "coro.h"
#include <optional>
#include <type_traits>
#include <exception>
#include <utility>
#include <type_traits>
namespace dpp {
namespace detail {
namespace coroutine {
template <typename R>
struct promise_t;
template <typename R>
/**
* @brief Alias for the handle_t of a coroutine.
*/
using handle_t = std_coroutine::coroutine_handle<promise_t<R>>;
/**
* @brief Base class of dpp::coroutine<R>.
*
* @warn This class should not be used directly by a user, use dpp::coroutine<R> instead.
* @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::coroutine<R> so a user cannot call await_suspend and await_resume directly.
*/
template <typename R>
class coroutine_base {
protected:
/**
* @brief Promise has friend access for the constructor
*/
friend struct promise_t<R>;
/**
* @brief Coroutine handle.
*/
detail::coroutine::handle_t<R> handle{nullptr};
private:
/**
* @brief Construct from a handle. Internal use only.
*/
coroutine_base(detail::coroutine::handle_t<R> h) : handle{h} {}
public:
/**
* @brief Default constructor, creates an empty coroutine.
*/
coroutine_base() = default;
/**
* @brief Copy constructor is disabled
*/
coroutine_base(const coroutine_base &) = delete;
/**
* @brief Move constructor, grabs another coroutine's handle
*
* @param other Coroutine to move the handle from
*/
coroutine_base(coroutine_base &&other) noexcept : handle(std::exchange(other.handle, nullptr)) {}
/**
* @brief Destructor, destroys the handle.
*/
~coroutine_base() {
if (handle) {
handle.destroy();
}
}
/**
* @brief Copy assignment is disabled
*/
coroutine_base &operator=(const coroutine_base &) = delete;
/**
* @brief Move assignment, grabs another coroutine's handle
*
* @param other Coroutine to move the handle from
*/
coroutine_base &operator=(coroutine_base &&other) noexcept {
handle = std::exchange(other.handle, nullptr);
return *this;
}
/**
* @brief First function called by the standard library when the coroutine is co_await-ed.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @throws invalid_operation_exception if the coroutine is empty or finished.
* @return bool Whether the coroutine is done
*/
[[nodiscard]] bool await_ready() const {
if (!handle) {
throw dpp::logic_exception("cannot co_await an empty coroutine");
}
return handle.done();
}
/**
* @brief Second function called by the standard library when the coroutine is co_await-ed.
*
* Stores the calling coroutine in the promise to resume when this coroutine suspends.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @param caller The calling coroutine, now suspended
*/
template <typename T>
[[nodiscard]] handle_t<R> await_suspend(detail::std_coroutine::coroutine_handle<T> caller) noexcept {
handle.promise().parent = caller;
return handle;
}
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @throw Throws any exception thrown or uncaught by the coroutine
* @return R The result of the coroutine. It is given to the caller as a result to `co_await`
*/
decltype(auto) await_resume() & {
return static_cast<dpp::coroutine<R> &>(*this).await_resume_impl();
}
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @throw Throws any exception thrown or uncaught by the coroutine
* @return R The result of the coroutine. It is given to the caller as a result to `co_await`
*/
[[nodiscard]] decltype(auto) await_resume() const & {
return static_cast<dpp::coroutine<R> const&>(*this).await_resume_impl();
}
/**
* @brief Function called by the standard library when the coroutine is resumed.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @throw Throws any exception thrown or uncaught by the coroutine
* @return R The result of the coroutine. It is given to the caller as a result to `co_await`
*/
[[nodiscard]] decltype(auto) await_resume() && {
return static_cast<dpp::coroutine<R> &&>(*this).await_resume_impl();
}
};
} // namespace coroutine
} // namespace detail
/**
* @class coroutine coroutine.h coro/coroutine.h
* @brief Base type for a coroutine, starts on co_await.
*
* @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs.
* Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub Issues</a> or to our <a href="https://discord.gg/dpp">Discord Server</a>.
* @warning - Using co_await on this object more than once is undefined behavior.
* @tparam R Return type of the coroutine. Can be void, or a complete object that supports move construction and move assignment.
*/
template <typename R>
class coroutine : private detail::coroutine::coroutine_base<R> {
/**
* @brief Internal use only base class containing common logic between coroutine<R> and coroutine<void>. It also serves to prevent await_suspend and await_resume from being used directly.
*
* @warning For internal use only, do not use.
* @see operator co_await()
*/
friend class detail::coroutine::coroutine_base<R>;
[[nodiscard]] R& await_resume_impl() & {
detail::coroutine::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *promise.result;
}
[[nodiscard]] const R& await_resume_impl() const & {
detail::coroutine::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *promise.result;
}
[[nodiscard]] R&& await_resume_impl() && {
detail::coroutine::promise_t<R> &promise = this->handle.promise();
if (promise.exception) {
std::rethrow_exception(promise.exception);
}
return *std::move(promise.result);
}
public:
#ifdef _DOXYGEN_ // :))))
/**
* @brief Default constructor, creates an empty coroutine.
*/
coroutine() = default;
/**
* @brief Copy constructor is disabled
*/
coroutine(const coroutine &) = delete;
/**
* @brief Move constructor, grabs another coroutine's handle
*
* @param other Coroutine to move the handle from
*/
coroutine(coroutine &&other) noexcept;
/**
* @brief Destructor, destroys the handle.
*/
~coroutine();
/**
* @brief Copy assignment is disabled
*/
coroutine &operator=(const coroutine &) = delete;
/**
* @brief Move assignment, grabs another coroutine's handle
*
* @param other Coroutine to move the handle from
*/
coroutine &operator=(coroutine &&other) noexcept;
/**
* @brief First function called by the standard library when the coroutine is co_await-ed.
*
* @remark Do not call this manually, use the co_await keyword instead.
* @throws invalid_operation_exception if the coroutine is empty or finished.
* @return bool Whether the coroutine is done
*/
[[nodiscard]] bool await_ready() const;
#else
using detail::coroutine::coroutine_base<R>::coroutine_base; // use coroutine_base's constructors
using detail::coroutine::coroutine_base<R>::operator=; // use coroutine_base's assignment operators
using detail::coroutine::coroutine_base<R>::await_ready; // expose await_ready as public
#endif
/**
* @brief Suspend the caller until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a reference.
*/
[[nodiscard]] auto& operator co_await() & noexcept {
return static_cast<detail::coroutine::coroutine_base<R>&>(*this);
}
/**
* @brief Suspend the caller until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a const reference.
*/
[[nodiscard]] const auto& operator co_await() const & noexcept {
return static_cast<detail::coroutine::coroutine_base<R> const&>(*this);
}
/**
* @brief Suspend the caller until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as an rvalue reference.
*/
[[nodiscard]] auto&& operator co_await() && noexcept {
return static_cast<detail::coroutine::coroutine_base<R>&&>(*this);
}
};
#ifndef _DOXYGEN_ // don't generate this on doxygen because `using` doesn't work and 2 copies of coroutine_base's docs is enough
/**
* @brief Base type for a coroutine, starts on co_await.
*
* @warning - This feature is EXPERIMENTAL. The API may change at any time and there may be bugs. Please report any to <a href="https://github.com/brainboxdotcc/DPP/issues">GitHub issues</a> or to the <a href="https://discord.gg/dpp">D++ Discord server</a>.
* @warning - Using co_await on this object more than once is undefined behavior.
* @tparam R Return type of the coroutine. Can be void, or a complete object that supports move construction and move assignment.
*/
template <>
class coroutine<void> : private detail::coroutine::coroutine_base<void> {
/**
* @brief Base class has friend access for CRTP downcast
*/
friend class detail::coroutine::coroutine_base<void>;
void await_resume_impl() const;
public:
using detail::coroutine::coroutine_base<void>::coroutine_base; // use coroutine_base's constructors
using detail::coroutine::coroutine_base<void>::operator=; // use coroutine_base's assignment operators
using detail::coroutine::coroutine_base<void>::await_ready; // expose await_ready as public
/**
* @brief Suspend the current coroutine until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a reference.
*/
[[nodiscard]] auto& operator co_await() & noexcept {
return static_cast<detail::coroutine::coroutine_base<void>&>(*this);
}
/**
* @brief Suspend the current coroutine until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as a const reference.
*/
[[nodiscard]] const auto& operator co_await() const & noexcept {
return static_cast<detail::coroutine::coroutine_base<void> const &>(*this);
}
/**
* @brief Suspend the current coroutine until the coroutine completes.
*
* @throw On resumption, any exception thrown by the coroutine is propagated to the caller.
* @return On resumption, this expression evaluates to the result object of type R, as an rvalue reference.
*/
[[nodiscard]] auto&& operator co_await() && noexcept {
return static_cast<detail::coroutine::coroutine_base<void>&&>(*this);
}
};
#endif /* _DOXYGEN_ */
namespace detail::coroutine {
template <typename R>
struct final_awaiter;
#ifdef DPP_CORO_TEST
struct promise_t_base{};
#endif
/**
* @brief Promise type for coroutine.
*/
template <typename R>
struct promise_t {
/**
* @brief Handle of the coroutine co_await-ing this coroutine.
*/
std_coroutine::coroutine_handle<> parent{nullptr};
/**
* @brief Return value of the coroutine
*/
std::optional<R> result{};
/**
* @brief Pointer to an uncaught exception thrown by the coroutine
*/
std::exception_ptr exception{nullptr};
#ifdef DPP_CORO_TEST
promise_t() {
++coro_alloc_count<promise_t_base>;
}
~promise_t() {
--coro_alloc_count<promise_t_base>;
}
#endif
/**
* @brief Function called by the standard library when reaching the end of a coroutine
*
* @return final_awaiter<R> Resumes any coroutine co_await-ing on this
*/
[[nodiscard]] final_awaiter<R> final_suspend() const noexcept;
/**
* @brief Function called by the standard library when the coroutine start
*
* @return @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_always">std::suspend_always</a> Always suspend at the start, for a lazy start
*/
[[nodiscard]] std_coroutine::suspend_always initial_suspend() const noexcept {
return {};
}
/**
* @brief Function called when an exception escapes the coroutine
*
* Stores the exception to throw to the co_await-er
*/
void unhandled_exception() noexcept {
exception = std::current_exception();
}
/**
* @brief Function called by the standard library when the coroutine co_returns a value.
*
* Stores the value internally to hand to the caller when it resumes.
*
* @param expr The value given to co_return
*/
void return_value(R&& expr) noexcept(std::is_nothrow_move_constructible_v<R>) requires std::move_constructible<R> {
result = static_cast<R&&>(expr);
}
/**
* @brief Function called by the standard library when the coroutine co_returns a value.
*
* Stores the value internally to hand to the caller when it resumes.
*
* @param expr The value given to co_return
*/
void return_value(const R &expr) noexcept(std::is_nothrow_copy_constructible_v<R>) requires std::copy_constructible<R> {
result = expr;
}
/**
* @brief Function called by the standard library when the coroutine co_returns a value.
*
* Stores the value internally to hand to the caller when it resumes.
*
* @param expr The value given to co_return
*/
template <typename T>
requires (!std::is_same_v<R, std::remove_cvref_t<T>> && std::convertible_to<T, R>)
void return_value(T&& expr) noexcept (std::is_nothrow_convertible_v<T, R>) {
result = std::forward<T>(expr);
}
/**
* @brief Function called to get the coroutine object
*/
dpp::coroutine<R> get_return_object() {
return dpp::coroutine<R>{handle_t<R>::from_promise(*this)};
}
};
/**
* @brief Struct returned by a coroutine's final_suspend, resumes the continuation
*/
template <typename R>
struct final_awaiter {
/**
* @brief First function called by the standard library when reaching the end of a coroutine
*
* @return false Always return false, we need to suspend to resume the parent
*/
[[nodiscard]] bool await_ready() const noexcept {
return false;
}
/**
* @brief Second function called by the standard library when reaching the end of a coroutine.
*
* @return std::handle_t<> Coroutine handle to resume, this is either the parent if present or std::noop_coroutine()
*/
[[nodiscard]] std_coroutine::coroutine_handle<> await_suspend(std_coroutine::coroutine_handle<promise_t<R>> handle) const noexcept {
auto parent = handle.promise().parent;
return parent ? parent : std_coroutine::noop_coroutine();
}
/**
* @brief Function called by the standard library when this object is resumed
*/
void await_resume() const noexcept {}
};
template <typename R>
final_awaiter<R> promise_t<R>::final_suspend() const noexcept {
return {};
}
/**
* @brief Struct returned by a coroutine's final_suspend, resumes the continuation
*/
template <>
struct promise_t<void> {
/**
* @brief Handle of the coroutine co_await-ing this coroutine.
*/
std_coroutine::coroutine_handle<> parent{nullptr};
/**
* @brief Pointer to an uncaught exception thrown by the coroutine
*/
std::exception_ptr exception{nullptr};
/**
* @brief Function called by the standard library when reaching the end of a coroutine
*
* @return final_awaiter<R> Resumes any coroutine co_await-ing on this
*/
[[nodiscard]] final_awaiter<void> final_suspend() const noexcept {
return {};
}
/**
* @brief Function called by the standard library when the coroutine start
*
* @return @return <a href="https://en.cppreference.com/w/cpp/coroutine/suspend_always">std::suspend_always</a> Always suspend at the start, for a lazy start
*/
[[nodiscard]] std_coroutine::suspend_always initial_suspend() const noexcept {
return {};
}
/**
* @brief Function called when an exception escapes the coroutine
*
* Stores the exception to throw to the co_await-er
*/
void unhandled_exception() noexcept {
exception = std::current_exception();
}
/**
* @brief Function called when co_return is used
*/
void return_void() const noexcept {}
/**
* @brief Function called to get the coroutine object
*/
[[nodiscard]] dpp::coroutine<void> get_return_object() {
return dpp::coroutine<void>{handle_t<void>::from_promise(*this)};
}
};
} // namespace detail
#ifndef _DOXYGEN_
inline void coroutine<void>::await_resume_impl() const {
if (handle.promise().exception) {
std::rethrow_exception(handle.promise().exception);
}
}
#endif /* _DOXYGEN_ */
DPP_CHECK_ABI_COMPAT(coroutine<void>, coroutine_dummy)
DPP_CHECK_ABI_COMPAT(coroutine<uint64_t>, coroutine_dummy)
} // namespace dpp
/**
* @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function.
*/
template<typename R, typename... Args>
struct dpp::detail::std_coroutine::coroutine_traits<dpp::coroutine<R>, Args...> {
using promise_type = dpp::detail::coroutine::promise_t<R>;
};
#endif /* DPP_CORO */