/************************************************************************************ * * 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 namespace dpp { struct coroutine_dummy { int *handle_dummy = nullptr; }; } #ifdef DPP_CORO #include "coro.h" #include #include #include #include #include namespace dpp { namespace detail { namespace coroutine { template struct promise_t; template /** * @brief Alias for the handle_t of a coroutine. */ using handle_t = std_coroutine::coroutine_handle>; /** * @brief Base class of dpp::coroutine. * * @warn This class should not be used directly by a user, use dpp::coroutine instead. * @note This class contains all the functions used internally by co_await. It is intentionally opaque and a private base of dpp::coroutine so a user cannot call await_suspend and await_resume directly. */ template class coroutine_base { protected: /** * @brief Promise has friend access for the constructor */ friend struct promise_t; /** * @brief Coroutine handle. */ detail::coroutine::handle_t handle{nullptr}; private: /** * @brief Construct from a handle. Internal use only. */ coroutine_base(detail::coroutine::handle_t 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 [[nodiscard]] handle_t await_suspend(detail::std_coroutine::coroutine_handle 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 &>(*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 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 &&>(*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 GitHub Issues or to our Discord Server. * @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 : private detail::coroutine::coroutine_base { /** * @brief Internal use only base class containing common logic between coroutine and coroutine. 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; [[nodiscard]] R& await_resume_impl() & { detail::coroutine::promise_t &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 &promise = this->handle.promise(); if (promise.exception) { std::rethrow_exception(promise.exception); } return *promise.result; } [[nodiscard]] R&& await_resume_impl() && { detail::coroutine::promise_t &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::coroutine_base; // use coroutine_base's constructors using detail::coroutine::coroutine_base::operator=; // use coroutine_base's assignment operators using detail::coroutine::coroutine_base::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&>(*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 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&&>(*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 GitHub issues or to the D++ Discord server. * @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 : private detail::coroutine::coroutine_base { /** * @brief Base class has friend access for CRTP downcast */ friend class detail::coroutine::coroutine_base; void await_resume_impl() const; public: using detail::coroutine::coroutine_base::coroutine_base; // use coroutine_base's constructors using detail::coroutine::coroutine_base::operator=; // use coroutine_base's assignment operators using detail::coroutine::coroutine_base::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&>(*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 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&&>(*this); } }; #endif /* _DOXYGEN_ */ namespace detail::coroutine { template struct final_awaiter; #ifdef DPP_CORO_TEST struct promise_t_base{}; #endif /** * @brief Promise type for coroutine. */ template 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 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() { --coro_alloc_count; } #endif /** * @brief Function called by the standard library when reaching the end of a coroutine * * @return final_awaiter Resumes any coroutine co_await-ing on this */ [[nodiscard]] final_awaiter final_suspend() const noexcept; /** * @brief Function called by the standard library when the coroutine start * * @return @return std::suspend_always 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) requires std::move_constructible { result = static_cast(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) requires std::copy_constructible { 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 requires (!std::is_same_v> && std::convertible_to) void return_value(T&& expr) noexcept (std::is_nothrow_convertible_v) { result = std::forward(expr); } /** * @brief Function called to get the coroutine object */ dpp::coroutine get_return_object() { return dpp::coroutine{handle_t::from_promise(*this)}; } }; /** * @brief Struct returned by a coroutine's final_suspend, resumes the continuation */ template 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> 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 final_awaiter promise_t::final_suspend() const noexcept { return {}; } /** * @brief Struct returned by a coroutine's final_suspend, resumes the continuation */ template <> struct promise_t { /** * @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 Resumes any coroutine co_await-ing on this */ [[nodiscard]] final_awaiter final_suspend() const noexcept { return {}; } /** * @brief Function called by the standard library when the coroutine start * * @return @return std::suspend_always 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 get_return_object() { return dpp::coroutine{handle_t::from_promise(*this)}; } }; } // namespace detail #ifndef _DOXYGEN_ inline void coroutine::await_resume_impl() const { if (handle.promise().exception) { std::rethrow_exception(handle.promise().exception); } } #endif /* _DOXYGEN_ */ DPP_CHECK_ABI_COMPAT(coroutine, coroutine_dummy) DPP_CHECK_ABI_COMPAT(coroutine, coroutine_dummy) } // namespace dpp /** * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. */ template struct dpp::detail::std_coroutine::coroutine_traits, Args...> { using promise_type = dpp::detail::coroutine::promise_t; }; #endif /* DPP_CORO */