From 97f3f2873fd700d24b31f4d5c357210b45053a94 Mon Sep 17 00:00:00 2001 From: Lukas Thomann Date: Sun, 28 Jun 2026 20:44:46 +0200 Subject: [PATCH] added templated payload to be able to support custom tags with custom context --- cth_coro/incl/cth/coro/executor.hpp | 72 +++++++----- cth_coro/incl/cth/coro/func/steal.hpp | 53 +++++++-- cth_coro/incl/cth/coro/scheduler.hpp | 4 +- .../awaiters/this_coro_promise_awaiter.hpp | 7 +- .../this_coro_promise_awaiter_base.hpp | 18 ++- .../cth/coro/tasks/dev/scheduled_task.hpp | 22 ++-- .../incl/cth/coro/tasks/executor_task.hpp | 29 +++-- .../tasks/promises/this_coro_promise_base.hpp | 41 ++++--- cth_coro/incl/cth/coro/tasks/sync_task.hpp | 22 ++-- cth_coro/incl/cth/coro/this_coro/payload.hpp | 7 +- .../incl/cth/coro/this_coro/scheduler_tag.hpp | 2 +- cth_coro/incl/cth/coro/this_coro/wait_tag.hpp | 2 +- cth_coro/incl/cth/coro/utility/concepts.hpp | 20 +++- cth_coro/incl/cth/coro/utility/fwd.hpp | 58 ++++++---- .../cth/coro/utility/independent_concepts.hpp | 11 ++ cth_coro/tests/src/this_coro/payload_test.cpp | 107 ++++++++++++++++++ 16 files changed, 338 insertions(+), 137 deletions(-) create mode 100644 cth_coro/incl/cth/coro/utility/independent_concepts.hpp create mode 100644 cth_coro/tests/src/this_coro/payload_test.cpp diff --git a/cth_coro/incl/cth/coro/executor.hpp b/cth_coro/incl/cth/coro/executor.hpp index 0067ae0..73c0aa8 100644 --- a/cth_coro/incl/cth/coro/executor.hpp +++ b/cth_coro/incl/cth/coro/executor.hpp @@ -26,55 +26,65 @@ class executor { /** - * steals the execution context to the scheduler, keeps it as awaitable + * steals the execution context to the scheduler after the coroutine finished, keeps it as awaitable + * @tparam Pyld payload type; the scheduler is injected automatically when it is a scheduler_payload * @tparam Awaitable must be compatible with the this_coro framework * @param awaitable to steal context from + * @param args extra arguments to construct the payload with (besides the auto-injected scheduler) * @return awaitable with context stolen */ - template - auto steal(Awaitable&& awaitable) -> awaiter_t { return co::steal(scheduler(), awaitable); } + template + [[nodiscard]] auto steal(Awaitable&& awaitable, Args&&... args) const -> awaiter_t { + if constexpr(this_coro::scheduler_payload) + return co::steal(std::forward(awaitable), Pyld{scheduler(), std::forward(args)...}); + else + return co::steal(std::forward(awaitable), Pyld{std::forward(args)...}); + } /** - * steals the execution context to the scheduler + * steals the execution context to the scheduler after the coroutine finished * @note converts the awaitable to a task + * @tparam Pyld payload type; the scheduler is injected automatically when it is a scheduler_payload * @tparam Awaitable must not be compatible with the this_coro framework * @param awaitable to steal context from + * @param args extra arguments to construct the payload with (besides the auto-injected scheduler) * @return task with context stolen */ - template - auto steal(Awaitable awaitable) -> capture_task> { - return co::steal(scheduler(), std::move(awaitable)); + template + [[nodiscard]] auto steal(Awaitable awaitable, Args&&... args) const -> capture_task> { + if constexpr(this_coro::scheduler_payload) + return co::steal(std::move(awaitable), Pyld{scheduler(), std::forward(args)...}); + else + return co::steal(std::move(awaitable), Pyld{std::forward(args)...}); } - /** - * runs the task on the scheduler once started - * @tparam T type to return - * @param task to run + * runs the coroutine on the scheduler once started, then captures execution back + * @tparam Pyld payload type; the scheduler is injected automatically when it is a scheduler_payload + * @param task coroutine to run on the scheduler + * @param args extra arguments to construct the payload with (besides the auto-injected scheduler) + * @return cold scheduled task; starts on the scheduler when awaited */ - template - auto spawn(scheduled_task task) -> scheduled_task { return executor::steal(std::move(task)); } - - /** - * wraps the awaitable with a task running on the scheduler once started - * @param awaitable to wrap - * @return task - */ - template - auto spawn(Awaitable awaitable) -> scheduled_task> { - return executor::spawn(this_coro::payload{scheduler()}, *this, std::move(awaitable)); + template + [[nodiscard]] auto spawn(Awaitable task, Args&&... args) const -> scheduled_task, Pyld> { + if constexpr(this_coro::scheduler_payload) + return spawn_impl(Pyld{scheduler(), std::forward(args)...}, *this, std::move(task)); + else + return spawn_impl(Pyld{std::forward(args)...}, *this, std::move(task)); } private: - template - static auto spawn( - [[maybe_unused]] this_coro::payload p, - executor& s, + template + static auto spawn_impl( + Pyld payload, + executor self, Awaitable task ) - -> scheduled_task> { - co_await s.schedule(); - co_return co_await s.steal(std::move(task)); + -> scheduled_task, Pyld> { + co_await self.schedule(); + + // inject the payload into the child and finish back on this executor + co_return co_await co::steal(std::move(task), payload); } scheduler const* _sched; @@ -99,7 +109,9 @@ class executor { namespace cth::co::this_coro { struct [[nodiscard]] executor_tag : tag_base { - static [[nodiscard]] constexpr auto operator()(payload const& p) { return data_awaiter{executor{p.scheduler()}}; } + static [[nodiscard]] constexpr auto operator()(default_payload const& p) { + return data_awaiter{executor{p.scheduler()}}; + } }; diff --git a/cth_coro/incl/cth/coro/func/steal.hpp b/cth_coro/incl/cth/coro/func/steal.hpp index 1140375..0df673e 100644 --- a/cth_coro/incl/cth/coro/func/steal.hpp +++ b/cth_coro/incl/cth/coro/func/steal.hpp @@ -10,29 +10,60 @@ namespace cth::co { -template -[[nodiscard]] auto steal(scheduler const& scheduler, Awaitable awaitable) - -> capture_task> { + +template +[[nodiscard]] auto steal(Awaitable awaitable, Pyld const& payload) -> capture_task> { + static_assert(has_scheduler, "foreign awaitables can only be stolen with a scheduler in the payload"); + using result_t = awaited_t; if constexpr(std::same_as) { - co_await awaitable; - co_await schedule_awaiter{scheduler}; + co_await std::move(awaitable); + co_await schedule_awaiter{payload.scheduler()}; co_return; } else { - decltype(auto) result = co_await awaitable; - co_await schedule_awaiter{scheduler}; + decltype(auto) result = co_await std::move(awaitable); + co_await schedule_awaiter{payload.scheduler()}; co_return result; } } -template -constexpr auto steal(scheduler const& scheduler, Awaitable&& awaitable) -> awaiter_t { + +template +constexpr auto steal(Awaitable&& awaitable, Pyld const& payload) -> awaiter_t { + static_assert(this_coro_awaitable_with, "awaitable not compatible with payload type"); + auto awaiter = co::extract_awaiter(std::forward(awaitable)); - awaiter.inject(this_coro::payload{scheduler}); - + awaiter.inject(payload); + return awaiter; } +template +constexpr auto steal(Awaitable&& awaitable, Args&&... args) -> awaiter_t { + return co::steal(std::forward(awaitable), Pyld{std::forward(args)...}); +} + +template +[[nodiscard]] auto steal(Awaitable awaitable, Args&&... args) -> capture_task> { + return co::steal(std::move(awaitable), Pyld{std::forward(args)...}); +} + + + +template +[[nodiscard]] auto steal( + Awaitable awaitable, + scheduler const& scheduler +) -> capture_task> { + return co::steal(std::move(awaitable), this_coro::default_payload{scheduler}); +} + +template +constexpr auto steal(Awaitable&& awaitable, scheduler const& scheduler) -> awaiter_t { + return co::steal(std::forward(awaitable), this_coro::default_payload{scheduler}); +} + + } diff --git a/cth_coro/incl/cth/coro/scheduler.hpp b/cth_coro/incl/cth/coro/scheduler.hpp index 3849631..ee121cf 100644 --- a/cth_coro/incl/cth/coro/scheduler.hpp +++ b/cth_coro/incl/cth/coro/scheduler.hpp @@ -1,11 +1,13 @@ #pragma once -#include "cth/io/log.hpp" +#include +#include #include "os/native_handle.hpp" #include "utility/fwd.hpp" + #include #include #include diff --git a/cth_coro/incl/cth/coro/tasks/awaiters/this_coro_promise_awaiter.hpp b/cth_coro/incl/cth/coro/tasks/awaiters/this_coro_promise_awaiter.hpp index 295646c..073f4c2 100644 --- a/cth_coro/incl/cth/coro/tasks/awaiters/this_coro_promise_awaiter.hpp +++ b/cth_coro/incl/cth/coro/tasks/awaiters/this_coro_promise_awaiter.hpp @@ -8,10 +8,11 @@ namespace cth::co { template struct this_coro_promise_awaiter - : this_coro_promise_awaiter_base - , private dev::basic_promise_awaiter { + : this_coro_promise_awaiter_base, + private dev::basic_promise_awaiter { private: using base_t = dev::basic_promise_awaiter; + using payload_t = Promise::payload_type; public: this_coro_promise_awaiter() = delete; @@ -26,7 +27,7 @@ struct this_coro_promise_awaiter template decltype(auto) await_suspend(std::coroutine_handle caller) noexcept { - this_coro_promise_awaiter_base::inject_payload_into(base_t::handle); + this_coro_promise_awaiter_base::inject_payload_into(base_t::handle); return base_t::await_suspend(caller); } }; diff --git a/cth_coro/incl/cth/coro/tasks/awaiters/this_coro_promise_awaiter_base.hpp b/cth_coro/incl/cth/coro/tasks/awaiters/this_coro_promise_awaiter_base.hpp index 9534de7..b5c66c7 100644 --- a/cth_coro/incl/cth/coro/tasks/awaiters/this_coro_promise_awaiter_base.hpp +++ b/cth_coro/incl/cth/coro/tasks/awaiters/this_coro_promise_awaiter_base.hpp @@ -1,16 +1,22 @@ #pragma once #include "cth/coro/this_coro/payload.hpp" - #include "cth/coro/utility/concepts.hpp" + +#include + #include -#include + namespace cth::co { /** * Base for awaitables which support executor injection + * @tparam Pyld payload to work with defaulted to @ref this_coro::default_payload */ +template struct this_coro_promise_awaiter_base { + using payload_type = Pyld; + constexpr this_coro_promise_awaiter_base() = default; /** @@ -21,11 +27,11 @@ struct this_coro_promise_awaiter_base { */ template void inject_payload_into(std::coroutine_handle h) { - constexpr bool injectable = this_coro_supported_promise; + constexpr bool injectable = this_coro_supported_promise_with; static_assert( injectable, - "this coro supporting awaitables may not be awaited by awaitables without support" + "this coro supporting awaitables may not be awaited by awaitables without a compatible payload" ); @@ -37,13 +43,13 @@ struct this_coro_promise_awaiter_base { * injects the this_coro payload into this awaiter unless it already has one * @param payload to inject */ - constexpr void inject(this_coro::payload payload) { + constexpr void inject(Pyld const& payload) { if(!_payload) _payload.emplace(payload); } private: - std::optional _payload{}; + cth::dt::optional _payload{}; }; diff --git a/cth_coro/incl/cth/coro/tasks/dev/scheduled_task.hpp b/cth_coro/incl/cth/coro/tasks/dev/scheduled_task.hpp index 69ae9d2..6df67e0 100644 --- a/cth_coro/incl/cth/coro/tasks/dev/scheduled_task.hpp +++ b/cth_coro/incl/cth/coro/tasks/dev/scheduled_task.hpp @@ -9,25 +9,25 @@ namespace cth::co::dev { -template -struct scheduled_promise : basic_promise, this_coro_promise_base { - explicit scheduled_promise(this_coro::payload payload) : this_coro_promise_base{std::move(payload)} {} +template +struct scheduled_promise : basic_promise, this_coro_promise_base { + explicit scheduled_promise(Pyld const& payload) : this_coro_promise_base{payload} {} template - explicit scheduled_promise(this_coro::payload const& payload, Args&&...) : scheduled_promise{payload} {} + explicit scheduled_promise(Pyld const& payload, Args&&...) : scheduled_promise{payload} {} - scheduled_task get_return_object() noexcept; + scheduled_task get_return_object() noexcept; }; } namespace cth::co { -template +template class [[nodiscard]] scheduled_task - : public task_base, dev::capture_promise_awaiter> { - using base_t = task_base, dev::capture_promise_awaiter>; + : public task_base, dev::capture_promise_awaiter> { + using base_t = task_base, dev::capture_promise_awaiter>; using base_t::base_t; friend base_t::promise_type; @@ -40,9 +40,9 @@ class [[nodiscard]] scheduled_task scheduled_task& operator=(scheduled_task&& other) noexcept = default; }; -template -scheduled_task dev::scheduled_promise::get_return_object() noexcept { - return scheduled_task{std::coroutine_handle::from_promise(*this)}; +template +scheduled_task dev::scheduled_promise::get_return_object() noexcept { + return scheduled_task{std::coroutine_handle::from_promise(*this)}; } } diff --git a/cth_coro/incl/cth/coro/tasks/executor_task.hpp b/cth_coro/incl/cth/coro/tasks/executor_task.hpp index 6faa180..4383c41 100644 --- a/cth_coro/incl/cth/coro/tasks/executor_task.hpp +++ b/cth_coro/incl/cth/coro/tasks/executor_task.hpp @@ -1,21 +1,18 @@ #pragma once +#include "cth/coro/tasks/task_base.hpp" #include "cth/coro/tasks/awaiters/this_coro_promise_awaiter.hpp" -#include "promises/basic_promise.hpp" -#include "promises/this_coro_promise_base.hpp" +#include "cth/coro/tasks/promises/basic_promise.hpp" +#include "cth/coro/tasks/promises/this_coro_promise_base.hpp" -#include "cth/coro/tasks/task_base.hpp" +#include "cth/coro/utility/fwd.hpp" #include -namespace cth::co { -template -class executor_task; -} namespace cth::co::dev { -template -struct executor_promise : basic_promise, this_coro_promise_base { - executor_task get_return_object() noexcept; +template +struct executor_promise : basic_promise, this_coro_promise_base { + executor_task get_return_object() noexcept; }; } @@ -25,18 +22,18 @@ namespace cth::co { * Task to be spawned on the @ref executor * @tparam T task return type */ -template -class [[nodiscard]] executor_task : public task_base, this_coro_promise_awaiter> { - using base_t = task_base, this_coro_promise_awaiter>; +template +class [[nodiscard]] executor_task : public task_base, this_coro_promise_awaiter> { + using base_t = task_base, this_coro_promise_awaiter>; using base_t::base_t; friend base_t::promise_type; }; -template -auto dev::executor_promise::get_return_object() noexcept -> executor_task { - return executor_task{std::coroutine_handle>::from_promise(*this)}; +template +auto dev::executor_promise::get_return_object() noexcept -> executor_task { + return executor_task{std::coroutine_handle>::from_promise(*this)}; } using executor_void_task = executor_task; diff --git a/cth_coro/incl/cth/coro/tasks/promises/this_coro_promise_base.hpp b/cth_coro/incl/cth/coro/tasks/promises/this_coro_promise_base.hpp index 0081a83..8c05349 100644 --- a/cth_coro/incl/cth/coro/tasks/promises/this_coro_promise_base.hpp +++ b/cth_coro/incl/cth/coro/tasks/promises/this_coro_promise_base.hpp @@ -1,10 +1,11 @@ #pragma once +#include "cth/coro/utility.hpp" #include "cth/coro/func/steal.hpp" #include "cth/coro/tasks/dev/capture_task.hpp" #include "cth/coro/this_coro/payload.hpp" #include "cth/coro/utility/concepts.hpp" -#include +#include #include #include @@ -19,22 +20,22 @@ class executor; * @details * - children must forward the payload on construction */ +template struct this_coro_promise_base { + using payload_type = Pyld; template [[nodiscard]] constexpr decltype(auto) await_transform(Tag&& t) { static_assert( - requires(Tag t) { - { t.operator()(std::declval()) } -> awaitable; - }, - "payload must satisfy the tags call operator" + requires(Tag t) { { t.operator()(std::declval()) } -> awaitable; }, + "Pyld must satisfy the tags call operator" ); return std::forward(t).operator()(*_payload); } - template + template Awaitable> [[nodiscard]] constexpr auto await_transform(Awaitable&& awaitable) -> awaiter_t { auto awaiter = co::extract_awaiter(awaitable); @@ -43,27 +44,37 @@ struct this_coro_promise_base { return awaiter; } - template - [[nodiscard]] constexpr auto await_transform(Awaitable&& captured) { - return cth::co::extract_awaiter(captured); + // a this_coro awaitable whose payload Pyld cannot satisfy -- diagnose instead of falling through to + // "no viable await_transform" (it is not foreign: foreign excludes this_coro awaitables by design) + template + requires (this_coro_awaitable && !this_coro_awaitable_with) + [[nodiscard]] constexpr auto await_transform(Awaitable&& awaitable) -> awaiter_t { + static_assert( + this_coro_awaitable_with, + "awaited task carries a payload incompatible with this coroutine's payload" + ); + return co::extract_awaiter(std::forward(awaitable)); } - template + template + [[nodiscard]] constexpr auto await_transform(Awaitable&& captured) { return cth::co::extract_awaiter(captured); } + + template requires (this_coro::scheduler_payload) [[nodiscard]] auto await_transform(Awaitable awaitable) -> capture_task> { - return co::steal(_payload->scheduler(), std::move(awaitable)); + return co::steal(std::move(awaitable), *_payload); } /** - * injects the this_coro payload into the promise + * injects the this_coro default_payload into the promise * @param payload to inject */ - constexpr void inject(this_coro::payload payload) { _payload = payload; } + constexpr void inject(Pyld const& payload) { _payload = payload; } - this_coro_promise_base(this_coro::payload payload) noexcept : _payload{std::move(payload)} {} + this_coro_promise_base(Pyld const& payload) noexcept : _payload{payload} {} this_coro_promise_base() noexcept = default; private: - std::optional _payload{}; + cth::dt::optional _payload{}; }; } diff --git a/cth_coro/incl/cth/coro/tasks/sync_task.hpp b/cth_coro/incl/cth/coro/tasks/sync_task.hpp index e83566f..255bc54 100644 --- a/cth_coro/incl/cth/coro/tasks/sync_task.hpp +++ b/cth_coro/incl/cth/coro/tasks/sync_task.hpp @@ -12,8 +12,8 @@ namespace cth::co::dev { template -struct sync_promise_template - : sync_promise_base, basic_promise {}; +struct sync_promise_template : sync_promise_base, + basic_promise {}; template class Awaiter> class [[nodiscard]] sync_task_template : public task_base { @@ -31,7 +31,7 @@ namespace cth::co { template class sync_task; -template +template class sync_executor_task; } @@ -42,9 +42,9 @@ struct sync_promise : dev::sync_promise_template { }; -template -struct sync_executor_promise : this_coro_promise_base, sync_promise_template { - sync_executor_task get_return_object() noexcept; +template +struct sync_executor_promise : this_coro_promise_base, sync_promise_template { + sync_executor_task get_return_object() noexcept; }; } @@ -74,9 +74,9 @@ class sync_task : public dev::sync_task_template, dev::basi }; using sync_void_task = sync_task; -template +template class sync_executor_task : public dev::sync_task_template< - dev::sync_executor_promise, + dev::sync_executor_promise, this_coro_promise_awaiter > {}; @@ -90,8 +90,8 @@ auto sync_promise::get_return_object() noexcept -> sync_task { return {std::coroutine_handle>::from_promise(*this)}; } -template -auto sync_executor_promise::get_return_object() noexcept -> sync_executor_task { - return {std::coroutine_handle>::from_promise(*this)}; +template +auto sync_executor_promise::get_return_object() noexcept -> sync_executor_task { + return {std::coroutine_handle>::from_promise(*this)}; } } diff --git a/cth_coro/incl/cth/coro/this_coro/payload.hpp b/cth_coro/incl/cth/coro/this_coro/payload.hpp index db7230c..6653714 100644 --- a/cth_coro/incl/cth/coro/this_coro/payload.hpp +++ b/cth_coro/incl/cth/coro/this_coro/payload.hpp @@ -3,8 +3,8 @@ namespace cth::co::this_coro { -struct payload { - constexpr payload(co::scheduler const& scheduler) : _scheduler{&scheduler} {} +struct scheduler_payload_base { + constexpr scheduler_payload_base(co::scheduler const& scheduler) : _scheduler{&scheduler} {} constexpr co::scheduler const& scheduler() const { return *_scheduler; } @@ -12,4 +12,7 @@ struct payload { co::scheduler const* _scheduler; }; +template +concept scheduler_payload = std::is_base_of_v>; + } diff --git a/cth_coro/incl/cth/coro/this_coro/scheduler_tag.hpp b/cth_coro/incl/cth/coro/this_coro/scheduler_tag.hpp index 8cd018b..0bf7b07 100644 --- a/cth_coro/incl/cth/coro/this_coro/scheduler_tag.hpp +++ b/cth_coro/incl/cth/coro/this_coro/scheduler_tag.hpp @@ -6,7 +6,7 @@ namespace cth::co::this_coro { struct [[nodiscard]] scheduler_tag : tag_base { - static constexpr auto operator()(payload const& payload) { + static constexpr auto operator()(scheduler_payload auto const& payload) { return data_awaiter{payload.scheduler()}; } }; diff --git a/cth_coro/incl/cth/coro/this_coro/wait_tag.hpp b/cth_coro/incl/cth/coro/this_coro/wait_tag.hpp index ef0248c..a682c60 100644 --- a/cth_coro/incl/cth/coro/this_coro/wait_tag.hpp +++ b/cth_coro/incl/cth/coro/this_coro/wait_tag.hpp @@ -9,7 +9,7 @@ namespace cth::co::this_coro { struct [[nodiscard]] wait_tag : tag_base { constexpr explicit wait_tag(chrono::time_point_t t) : timePoint{t} {} - constexpr auto operator()(payload const& p) const { return wait_awaiter{timePoint, p.scheduler()}; } + constexpr auto operator()(scheduler_payload auto const& p) const { return wait_awaiter{timePoint, p.scheduler()}; } chrono::time_point_t timePoint; }; diff --git a/cth_coro/incl/cth/coro/utility/concepts.hpp b/cth_coro/incl/cth/coro/utility/concepts.hpp index 6ed9796..75af43f 100644 --- a/cth_coro/incl/cth/coro/utility/concepts.hpp +++ b/cth_coro/incl/cth/coro/utility/concepts.hpp @@ -15,16 +15,27 @@ concept tag = std::is_base_of_v>; namespace cth::co { +template +concept has_scheduler = requires(T const& t) { + { t.scheduler() } -> std::convertible_to; +}; + +template +concept this_coro_awaitable_with = + has_payload_type> + && std::convertible_to>::payload_type>; + +template +concept this_coro_supported_promise_with = + has_payload_type + && std::convertible_to; template -concept this_coro_awaitable = - std::is_base_of_v>>; +concept this_coro_awaitable = has_payload_type>; template concept captured_awaitable = co::awaitable && std::is_base_of_v>; -template -concept this_coro_supported_promise = std::is_base_of_v; template concept foreign_awaitable = @@ -33,6 +44,7 @@ concept foreign_awaitable = template concept non_this_coro_awaitable = awaitable && !this_coro_awaitable; + template concept sync_promise_type = std::is_base_of_v; diff --git a/cth_coro/incl/cth/coro/utility/fwd.hpp b/cth_coro/incl/cth/coro/utility/fwd.hpp index 81df30d..3e7bc29 100644 --- a/cth_coro/incl/cth/coro/utility/fwd.hpp +++ b/cth_coro/incl/cth/coro/utility/fwd.hpp @@ -1,50 +1,58 @@ #pragma once - -#include - +#include "independent_concepts.hpp" namespace cth::co { class scheduler; class executor; } +namespace cth::co::this_coro { +struct scheduler_payload_base; +using default_payload = scheduler_payload_base; + + +/** + * new this_coro tags must inherit from this + */ +struct [[nodiscard]] tag_base {}; +struct executor_tag; +struct scheduler_tag; + +struct wait_tag; + +} namespace cth::co { -template +template class executor_task; + +template +class scheduled_task; + +template +class sync_executor_task; + + template class capture_task; -template -class scheduled_task; } + +namespace cth::except { +class coro_exception; +} + namespace cth::co { struct schedule_awaiter; struct wait_awaiter; +template struct this_coro_promise_awaiter_base; +template struct this_coro_promise_base; + struct capture_awaiter_base; struct sync_promise_base; } - - -namespace cth::co::this_coro { -struct payload; - -/** - * new this_coro tags must inherit from this - */ -struct [[nodiscard]] tag_base {}; -struct executor_tag; -struct scheduler_tag; - -struct wait_tag; - -} - -namespace cth::except { -class coro_exception; -} diff --git a/cth_coro/incl/cth/coro/utility/independent_concepts.hpp b/cth_coro/incl/cth/coro/utility/independent_concepts.hpp new file mode 100644 index 0000000..191456d --- /dev/null +++ b/cth_coro/incl/cth/coro/utility/independent_concepts.hpp @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +namespace cth::co { +template +concept payload = std::copy_constructible>; + +template +concept has_payload_type = requires { typename std::remove_cvref_t::payload_type; }; +} diff --git a/cth_coro/tests/src/this_coro/payload_test.cpp b/cth_coro/tests/src/this_coro/payload_test.cpp new file mode 100644 index 0000000..8da3203 --- /dev/null +++ b/cth_coro/tests/src/this_coro/payload_test.cpp @@ -0,0 +1,107 @@ +#include "test.hpp" + +#include "cth/coro/awaiters/data_awaiter.hpp" +#include "cth/coro/executor.hpp" +#include "cth/coro/scheduler.hpp" +#include "cth/coro/tasks/executor_task.hpp" +#include "cth/coro/this_coro.hpp" + +#include + +namespace cth::co { + +namespace { + + // a custom payload: the scheduler context (so the built-in tags keep working) plus a marker value + struct tagged_payload : this_coro::scheduler_payload_base { + constexpr tagged_payload(co::scheduler const& s, int marker) + : this_coro::scheduler_payload_base{s}, _marker{marker} {} + + [[nodiscard]] constexpr int marker() const { return _marker; } + + private: + int _marker; + }; + + template + concept marked_payload = requires(P const& p) { + { p.marker() } -> std::convertible_to; + }; + + // a custom this_coro tag that reads the marker off whatever payload the coroutine carries + struct marker_tag : this_coro::tag_base { + static constexpr auto operator()(marked_payload auto const& p) { return data_awaiter{p.marker()}; } + }; + + inline constexpr marker_tag marker{}; + + // invariant guard for the exact regression that caused the empty-payload bug: the awaiter must + // expose payload_type, else this_coro tasks silently demote to the foreign/steal path and their + // payload optional is never injected. a compile error here beats a runtime deref of an empty optional. + static_assert(this_coro_awaitable>, "executor_task lost its this_coro classification"); + static_assert(this_coro_awaitable>); + +} // namespace + +THIS_CORO_TEST(custom_payload, marker_injected) { + scheduler sched{autostart, 1}; + executor exec{sched}; + + auto task = []() -> executor_task { co_return co_await marker; }; + + auto const actual = sync(exec.spawn(task(), 42)); + EXPECT_EQ(actual, 42); +} + +THIS_CORO_TEST(custom_payload, propagates_through_nested) { + scheduler sched{autostart, 1}; + executor exec{sched}; + + auto child = []() -> executor_task { co_return co_await marker; }; + auto root = [](auto c) -> executor_task { co_return co_await c(); }; + + auto const actual = sync(exec.spawn(root(child), 7)); + EXPECT_EQ(actual, 7); +} + +THIS_CORO_TEST(custom_payload, deep_recursion_propagation) { + scheduler sched{autostart, 1}; + executor exec{sched}; + + struct Recursive { + auto operator()(int depth) -> executor_task { + if(depth == 0) + co_return co_await marker; + co_return co_await (*this)(depth - 1); + } + }; + + auto const actual = sync(exec.spawn(Recursive{}(8), 99)); + EXPECT_EQ(actual, 99); +} + +THIS_CORO_TEST(custom_payload, builtin_executor_tag_coexists) { + scheduler sched{autostart, 1}; + executor exec{sched}; + + // tagged_payload derives scheduler_payload_base, so the built-in tags still resolve against it + auto task = []() -> executor_task { co_return co_await this_coro::executor; }; + + auto const actual = sync(exec.spawn(task(), 1)); + EXPECT_EQ(actual, exec); +} + +THIS_CORO_TEST(custom_payload, custom_and_builtin_tags_in_one_task) { + scheduler sched{autostart, 1}; + executor exec{sched}; + + auto task = []() -> executor_task { + (void)co_await this_coro::executor; // built-in tag resolves under the custom payload + co_return co_await marker; // custom tag reads the injected marker + }; + + auto const actual = sync(exec.spawn(task(), 5)); + EXPECT_EQ(actual, 5); +} + +}