mirror of
https://github.com/HappyTanuki/BumbleCee.git
synced 2025-12-18 05:03:28 +00:00
590 lines
18 KiB
C++
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 */
|