From 7dfb94022d1163f6f7749b24bc777c8808133c9e Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 20 Dec 2025 12:01:06 -0800 Subject: [PATCH] feat: coroutine types --- .cursorignore | 11 +++ .github/workflows/claude.yml | 58 --------------- include/boost/capy/async_result.hpp | 106 +++++++++++++++++++++++++++ include/boost/capy/detail/config.hpp | 12 +++ include/boost/capy/task.hpp | 103 ++++++++++++++++++++++++++ test/unit/application.cpp | 2 + test/unit/async_result.cpp | 35 +++++++++ test/unit/task.cpp | 46 ++++++++++++ 8 files changed, 315 insertions(+), 58 deletions(-) create mode 100644 .cursorignore delete mode 100644 .github/workflows/claude.yml create mode 100644 include/boost/capy/async_result.hpp create mode 100644 include/boost/capy/task.hpp create mode 100644 test/unit/async_result.cpp create mode 100644 test/unit/task.cpp diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..e0750d8 --- /dev/null +++ b/.cursorignore @@ -0,0 +1,11 @@ +* +!doc/ +!doc/** +!example/ +!example/** +!include/ +!include/** +!src/ +!src/** +!test/ +!test/** diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index c264a6a..0000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - issues: write - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - - # Optional: Customize the trigger phrase (default: @claude) - # trigger_phrase: "/claude" - - # Optional: Trigger when specific user is assigned to an issue - # assignee_trigger: "claude-bot" - - # Optional: Configure Claude's behavior with CLI arguments - # claude_args: | - # --model claude-opus-4-1-20250805 - # --max-turns 10 - # --allowedTools "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)" - # --system-prompt "Follow our coding standards. Ensure all new code has tests. Use TypeScript for new files." - - # Optional: Advanced settings configuration - # settings: | - # { - # "env": { - # "NODE_ENV": "test" - # } - # } \ No newline at end of file diff --git a/include/boost/capy/async_result.hpp b/include/boost/capy/async_result.hpp new file mode 100644 index 0000000..819fdee --- /dev/null +++ b/include/boost/capy/async_result.hpp @@ -0,0 +1,106 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_ASYNC_RESULT_HPP +#define BOOST_CAPY_ASYNC_RESULT_HPP + +#include + +#ifdef BOOST_CAPY_HAS_CORO + +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { + +template +class async_result +{ +public: + struct impl_base + { + virtual ~impl_base() = default; + virtual void start(std::function on_done) = 0; + virtual T get_result() = 0; + }; + +private: + std::unique_ptr impl_; + +public: + explicit async_result(std::unique_ptr p) : impl_(std::move(p)) {} + + async_result(async_result&&) = default; + async_result& operator=(async_result&&) = default; + + bool await_ready() const noexcept { return false; } + + void await_suspend(std::coroutine_handle<> h) + { + impl_->start([h]{ h.resume(); }); + } + + T await_resume() + { + return impl_->get_result(); + } +}; + +//----------------------------------------------------------------------------- + +template +struct async_result_impl : capy::async_result::impl_base +{ + DeferredOp op_; + std::variant result_; + + explicit async_result_impl(DeferredOp&& op) + : op_(std::forward(op)) + { + } + + void start(std::function on_done) override + { + std::move(op_)( + [this, on_done = std::move(on_done)](auto&&... args) mutable + { + result_.template emplace<1>(T{std::forward(args)...}); + on_done(); + }); + } + + T get_result() override + { + if (result_.index() == 0 && std::get<0>(result_)) + std::rethrow_exception(std::get<0>(result_)); + return std::move(std::get<1>(result_)); + } +}; + +//----------------------------------------------------------------------------- + +template +capy::async_result +make_async_result(DeferredOp&& op) +{ + using impl_type = async_result_impl>; + return capy::async_result( + std::make_unique(std::forward(op))); +} + +} // capy +} // boost + +#endif + +#endif diff --git a/include/boost/capy/detail/config.hpp b/include/boost/capy/detail/config.hpp index 68e8cfb..d8974fa 100644 --- a/include/boost/capy/detail/config.hpp +++ b/include/boost/capy/detail/config.hpp @@ -12,6 +12,10 @@ #include +#if __has_include() +# include +#endif + namespace boost { namespace capy { @@ -40,6 +44,14 @@ namespace capy { //------------------------------------------------ +#if defined(__cpp_lib_coroutine) && __cpp_lib_coroutine >= 201902L +# define BOOST_CAPY_HAS_CORO 1 +#elif defined(__cpp_impl_coroutine) && __cpp_impl_coroutines >= 201902L +# define BOOST_CAPY_HAS_CORO 1 +#endif + +//------------------------------------------------ + // Add source location to error codes #ifdef BOOST_CAPY_NO_SOURCE_LOCATION # define BOOST_CAPY_ERR(ev) (::boost::system::error_code(ev)) diff --git a/include/boost/capy/task.hpp b/include/boost/capy/task.hpp new file mode 100644 index 0000000..4c8d4bb --- /dev/null +++ b/include/boost/capy/task.hpp @@ -0,0 +1,103 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_TASK_HPP +#define BOOST_CAPY_TASK_HPP + +#include + +#ifdef BOOST_CAPY_HAS_CORO + +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { + +template +class task +{ +public: + struct promise_type + { + std::variant result_; + std::function on_done_; + + task get_return_object() + { + return task{std::coroutine_handle::from_promise(*this)}; + } + + std::suspend_always initial_suspend() noexcept { return {}; } + + auto final_suspend() noexcept + { + struct awaiter + { + promise_type* p_; + bool await_ready() noexcept { return false; } + void await_suspend(std::coroutine_handle<>) noexcept + { + if (p_->on_done_) + p_->on_done_(); + } + void await_resume() noexcept {} + }; + return awaiter{this}; + } + + void return_value(T v) { result_.template emplace<1>(std::move(v)); } + void unhandled_exception() { result_.template emplace<2>(std::current_exception()); } + }; + +private: + std::coroutine_handle h_; + +public: + explicit task(std::coroutine_handle h) : h_(h) {} + ~task() { if (h_) h_.destroy(); } + + task(task&& o) noexcept : h_(std::exchange(o.h_, {})) {} + task& operator=(task&&) = delete; + + // For awaiting from another task + bool await_ready() const noexcept { return false; } + + std::coroutine_handle<> await_suspend(std::coroutine_handle<> caller) noexcept + { + h_.promise().on_done_ = [caller]{ caller.resume(); }; + return h_; + } + + T await_resume() + { + auto& r = h_.promise().result_; + if (r.index() == 2) + std::rethrow_exception(std::get<2>(r)); + return std::move(std::get<1>(r)); + } + + // For external drivers + std::coroutine_handle handle() const noexcept { return h_; } + + std::coroutine_handle release() noexcept + { + return std::exchange(h_, {}); + } +}; + +} // capy +} // boost + +#endif + +#endif diff --git a/test/unit/application.cpp b/test/unit/application.cpp index bce2a56..400ce14 100644 --- a/test/unit/application.cpp +++ b/test/unit/application.cpp @@ -12,6 +12,8 @@ #include "test_suite.hpp" +#include + namespace boost { namespace capy { diff --git a/test/unit/async_result.cpp b/test/unit/async_result.cpp new file mode 100644 index 0000000..0ce34fe --- /dev/null +++ b/test/unit/async_result.cpp @@ -0,0 +1,35 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/beast2 +// + +// Test that header file is self-contained. +#include + +#ifdef BOOST_CAPY_HAS_CORO + +#include "test_suite.hpp" + +namespace boost { +namespace capy { + +struct async_result_test +{ + void + run() + { + } +}; + +TEST_SUITE( + async_result_test, + "boost.capy.async_result"); + +} // capy +} // boost + +#endif diff --git a/test/unit/task.cpp b/test/unit/task.cpp new file mode 100644 index 0000000..a08b490 --- /dev/null +++ b/test/unit/task.cpp @@ -0,0 +1,46 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/beast2 +// + +// Test that header file is self-contained. +#include + +#ifdef BOOST_CAPY_HAS_CORO + +#include "test_suite.hpp" + +namespace boost { +namespace capy { + +static +capy::task +handler() +{ + co_return 42; +} + +struct task_test +{ + void + run() + { + auto t = handler(); + while (!t.handle().done()) + t.handle().resume(); + BOOST_TEST_EQ(t.await_resume(), 42); + } +}; + +TEST_SUITE( + task_test, + "boost.capy.task"); + +} // capy +} // boost + +#endif