Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0f7a6f1
Revert "fix(coro): captured_awaitable tag, steal payload by value, no…
codethinki Oct 24, 2026
3de6c0a
fix(coro): task_base has no rvalue operator co_await
codethinki Oct 24, 2026
ada9134
fix(coro): every co_await returns the caller to its scheduler
codethinki Oct 25, 2026
695d5fd
feat(coro): executor::sync_wait spawns a task and blocks until it ends
codethinki Oct 25, 2026
6148271
feat(meta): nothrow_constructible and nothrow_copy_constructible conc…
codethinki Oct 25, 2026
bcd2861
refactor(coro): constexpr pass, copies are conditionally noexcept
codethinki Oct 25, 2026
2ddbe32
docs: README coro section, every co_await returns the caller, sync_wa…
codethinki Oct 25, 2026
27a110b
fix(win): keybd_event_awaiter derives the non-template osfence_awaite…
codethinki Oct 25, 2026
23cfc53
feat(coro): context interface, scheduler is a context
codethinki Oct 25, 2026
dca5272
feat(coro): strand, a serial queue on a scheduler
codethinki Oct 25, 2026
0d54d74
refactor(coro): reuse promise and awaiter bases, data_awaiter moves i…
codethinki Oct 25, 2026
1a3bb08
refactor(coro): one awaiter for all tasks, the promise attaches to th…
codethinki Oct 25, 2026
56fd031
refactor(coro): executor_promise::attach takes its noexcept from a tr…
codethinki Oct 25, 2026
528da0e
perf(meta): trait packs are type level, type_pack
codethinki Oct 25, 2026
abf0869
refactor(coro): caller_payload_t uses type_pack
codethinki Oct 25, 2026
03b6e54
fix(coro): start() on an active scheduler is a CTH_CRITICAL, callerCo…
codethinki Oct 25, 2026
1f8d169
fix(coro): a failed wait throws in the awaiting coroutine
codethinki Oct 25, 2026
7923154
refactor(coro): CTH_CORO_WARN, CTH_CORO_STABLE_ERR, CTH_CORO_STABLE_T…
codethinki Oct 25, 2026
5aa7783
refactor(coro): context::current() instead of the free function curre…
codethinki Oct 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ header only core
## cth::coro
coroutine runtime on asio

- `co::context`: interface of where coroutines run and wait, implemented by `scheduler` and `strand`
- `co::scheduler`: `asio::io_context` on its own threads
- `co::executor`: spawns tasks, moves execution back onto the scheduler after foreign awaitables
- `co::strand`: serial queue on a scheduler, its jobs run one at a time and in post order
- `co::executor`: spawns tasks, every `co_await` returns the caller to its context, except `schedule()`
- `this_coro::executor`, `this_coro::wait(duration)`, native handle waits

## cth::os
Expand Down Expand Up @@ -99,7 +101,7 @@ auto task = []() -> cth::co::executor_task<int> {
co_return 42;
};

int const result = cth::co::sync(executor.spawn(task()));
int const result = executor.sync_wait(task());
```

## logging
Expand Down
6 changes: 6 additions & 0 deletions cth/incl/cth/meta/concepts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ concept trivial = std::is_trivial_v<T>;
template<class T>
concept trivially_copyable = std::is_trivially_copyable_v<T>;

template<class T, class... Args>
concept nothrow_constructible = std::constructible_from<T, Args...> && std::is_nothrow_constructible_v<T, Args...>;

template<class T>
concept nothrow_copy_constructible = std::copy_constructible<T> && std::is_nothrow_copy_constructible_v<T>;

template<class T>
concept reference = std::is_reference_v<T>;

Expand Down
100 changes: 64 additions & 36 deletions cth/incl/cth/meta/traits/trait_packs.hpp
Original file line number Diff line number Diff line change
@@ -1,58 +1,86 @@
#pragma once
#include "cth/meta/utility.hpp"

#include <type_traits>
#include <utility>

namespace cth::mta {
namespace cth::mta::dev {
template<bool CanUnpack, template<class...> class Trait, class... Args>
struct unpacked {
using type = void;
};
template<template<class...> class Trait, class... Args>
struct unpacked<true, Trait, Args...> {
using type = Trait<Args...>;
};
}

namespace cth::mta {
/**
* a trait with its arguments, instantiated only when unpacked
* @details @ref type is Trait<Args...>, void if Trait<Args...> is ill-formed
*/
template<template<class...> class Trait, class... Args>
struct trait_pack {
static constexpr bool can_unpack = requires { typename Trait<Args...>; };

static constexpr auto unpack() {
if constexpr(can_unpack)
return std::type_identity<Trait<Args...>>{};
else
return std::type_identity<void>{};
}

using unpacked_type = type_of_t<unpack()>;
using unpacked_type = dev::unpacked<can_unpack, Trait, Args...>::type;
using type = unpacked_type;

static constexpr auto unpack() { return std::type_identity<unpacked_type>{}; }
};

template<class Pack, class... Rest>
consteval auto first_pack() {
if constexpr(Pack::can_unpack)
return Pack::unpack();
else if constexpr(!Pack::can_unpack && sizeof...(Rest) > 0)
return first_pack<Rest...>();
else
static_assert(false, "no unpackable trait found");
/**
* a pack that always unpacks to T
*/
template<class T>
using type_pack = trait_pack<std::type_identity_t, T>;
}

template<class... TraitPacks>
using first_pack_t = type_of_t<first_pack<TraitPacks...>()>;

namespace cth::mta::dev {
template<class Pack>
consteval auto unpack_trait() {
if constexpr(Pack::can_unpack)
return Pack::unpack();
else
static_assert(false, "Unable to unpack trait");
struct unpack_trait;

template<template<class...> class Trait, class... Args>
struct unpack_trait<trait_pack<Trait, Args...>> {
static_assert(requires { typename Trait<Args...>; }, "Unable to unpack trait");
using type = Trait<Args...>;
};

template<class... Packs>
struct first_pack {
static_assert(false, "no unpackable trait found");
using type = void;
};

template<template<class...> class Trait, class... Args, class... Rest>
struct first_pack<trait_pack<Trait, Args...>, Rest...> : first_pack<Rest...> {};

template<template<class...> class Trait, class... Args, class... Rest>
requires requires { typename Trait<Args...>; }
struct first_pack<trait_pack<Trait, Args...>, Rest...> {
using type = Trait<Args...>;
};
}

namespace cth::mta {
/**
* unpacks the pack
* @pre the pack can unpack
*/
template<class TraitPack>
using unpack_trait_t = decltype(unpack_trait<TraitPack>())::type;
using unpack_trait_t = dev::unpack_trait<TraitPack>::type;

template<bool Condition, class LeftPack, class RightPack>
consteval auto conditional_pack() {
if constexpr(Condition)
return unpack_trait<LeftPack>();
else
return unpack_trait<RightPack>();
}
/**
* unpacks the first pack that can unpack
*/
template<class... TraitPacks>
using first_pack_t = dev::first_pack<TraitPacks...>::type;

/**
* unpacks only the chosen pack
* @pre the chosen pack can unpack
*/
template<bool Condition, class LeftPack, class RightPack>
using conditional_pack_t = type_of_t<conditional_pack<Condition, LeftPack, RightPack>()>;
using conditional_pack_t =
std::conditional_t<Condition, dev::unpack_trait<LeftPack>, dev::unpack_trait<RightPack>>::type;

}
31 changes: 31 additions & 0 deletions cth/tests/src/meta/concepts_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,37 @@ TYPE_TEST(trivially_copyable, main) {
ASSERT_TRUE((satisfies<Point, CPT(trivially_copyable)>) );
}

TYPE_TEST(nothrow_constructible, main) {
struct Point {
float x, y;
};
struct throwing {
explicit throwing(int) noexcept(false) {}
};

ASSERT_TRUE((nothrow_constructible<int>) );
ASSERT_TRUE((nothrow_constructible<int, int>) );
ASSERT_TRUE((nothrow_constructible<Point>) );
ASSERT_FALSE((nothrow_constructible<throwing, int>) );
ASSERT_FALSE((nothrow_constructible<Point, std::string>) );
ASSERT_TRUE((satisfies<Point, CPT(nothrow_constructible)>) );
}

TYPE_TEST(nothrow_copy_constructible, main) {
struct Point {
float x, y;
};
struct throwing_copy {
throwing_copy() = default;
throwing_copy(throwing_copy const&) noexcept(false) {}
};

ASSERT_TRUE((nothrow_copy_constructible<int>) );
ASSERT_TRUE((nothrow_copy_constructible<Point>) );
ASSERT_FALSE((nothrow_copy_constructible<throwing_copy>) );
ASSERT_TRUE((satisfies<Point, CPT(nothrow_copy_constructible)>) );
}

TYPE_TEST(any_satisfy, main) {
ASSERT_TRUE((any_satisfy<CPT(arithmetic), int, float, double>) );
ASSERT_TRUE((any_satisfy<CPT(arithmetic), int, float, std::string>) );
Expand Down
7 changes: 7 additions & 0 deletions cth/tests/src/meta/traits/trait_packs_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ PACK_TEST(first_trait_pack, main) {
static_assert(std::same_as<first_pack_t<illegal_pack, illegal_pack, pack1>, pack1_t>);
}

PACK_TEST(type_pack, main) {
static_assert(type_pack<int>::can_unpack);
static_assert(std::same_as<unpack_trait_t<type_pack<int const&>>, int const&>);
static_assert(std::same_as<first_pack_t<illegal_pack, type_pack<void>>, void>);
static_assert(std::same_as<conditional_pack_t<false, illegal_pack, type_pack<float>>, float>);
}

PACK_TEST(conditional_trait_pack, main) {
static_assert(std::same_as<conditional_pack_t<true, pack1, pack2>, pack1_t>);
static_assert(std::same_as<conditional_pack_t<false, pack1, pack2>, pack2_t>);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
#pragma once
#include "cth/coro/scheduler.hpp"
#include "cth/coro/tasks/awaiters/dev/this_coro_awaiter_base.hpp"
#include "cth/coro/context.hpp"
#include "cth/coro/awaiters/base/wait_awaiter_base.hpp"
#include "cth/coro/utility/fwd.hpp"

#include <cth/os/wait.hpp>

#include <coroutine>


namespace cth::co {
template<this_coro::scheduler_payload Pyld = this_coro::default_payload>
struct native_handle_awaiter_base : dev::this_coro_awaiter_base<Pyld> {
/**
* base of awaiters that wait on a native handle
* @details resumes the caller on its context, a failed wait throws in the caller
* @attention a derived await_resume() calls @ref wait_awaiter_base::await_resume() first
*/
struct native_handle_awaiter_base : this_coro_awaiter_base, wait_awaiter_base {
constexpr explicit native_handle_awaiter_base(os::native_handle_t handle) : _handle{handle} {}

[[nodiscard]] bool await_ready() const { return os::check_ready(_handle); }
void await_suspend(std::coroutine_handle<> h) const {
this->payload().scheduler().await(_handle, [h] { h.resume(); });

/**
* resumes the caller on its context once the handle is signaled
* @param caller this_coro coroutine that awaits the handle
*/
template<class Caller>
void await_suspend(std::coroutine_handle<Caller> caller) {
static_assert(this_coro_compatible<Caller>, "native handle awaiters need a this_coro caller");
caller.promise().context().await(_handle, resume(caller));
}

private:
Expand Down
6 changes: 2 additions & 4 deletions cth_coro/incl/cth/coro/awaiters/base/osfence_awaiter_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
#include "cth/os/fence.hpp"

namespace cth::co {
template<this_coro::scheduler_payload Pyld = this_coro::default_payload>
struct osfence_awaiter_base : native_handle_awaiter_base<Pyld> {
explicit osfence_awaiter_base(os::fence const& fence) : native_handle_awaiter_base<Pyld>{
fence.native_handle()} {}
struct osfence_awaiter_base : native_handle_awaiter_base {
explicit osfence_awaiter_base(os::fence const& fence) : native_handle_awaiter_base{fence.native_handle()} {}
};
}
39 changes: 39 additions & 0 deletions cth_coro/incl/cth/coro/awaiters/base/wait_awaiter_base.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once
#include "cth/coro/exception.hpp"

#include <coroutine>
#include <system_error>


namespace cth::co {
/**
* base of awaiters that wait with @ref context::await(), a failed wait throws in the caller
* @attention a derived await_resume() calls the one of this base first, else a failed wait goes unnoticed
*/
class wait_awaiter_base {
public:
/**
* throws if the wait failed
* @throws cth::except::coro_exception reason: the wait failed
*/
void await_resume() const {
CTH_CORO_STABLE_THROW(_error, "wait failed, got {}", _error.message()) {}
}

protected:
/**
* gives the callback for @ref context::await(), it stores the error and resumes the coroutine
* @param h coroutine to resume
* @return callback for @ref context::await()
*/
[[nodiscard]] auto resume(std::coroutine_handle<> h) {
return [this, h](std::error_code ec) {
_error = ec;
h.resume();
};
}

private:
std::error_code _error{};
};
}
13 changes: 11 additions & 2 deletions cth_coro/incl/cth/coro/awaiters/data_awaiter.hpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
#pragma once
#include <cth/meta/concepts.hpp>

#include <coroutine>
#include <utility>


namespace cth::co {
template<class T>
class data_awaiter {
public:
constexpr explicit data_awaiter(T type) : _type{type} {}
constexpr explicit data_awaiter(T value) noexcept(mta::nothrow_constructible<T, T&&>) :
_type{std::forward<T>(value)} {}

[[nodiscard]] constexpr bool await_ready() const noexcept { return true; }

constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}

[[nodiscard]] constexpr T await_resume() const noexcept { return _type; }
/**
* @attention moves the value out, resume once
*/
[[nodiscard]] constexpr T await_resume() noexcept(mta::nothrow_constructible<T, T&&>) {
return std::forward<T>(_type);
}

private:
T _type;
Expand Down
19 changes: 11 additions & 8 deletions cth_coro/incl/cth/coro/awaiters/raw_native_handle_awaiter.hpp
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
#pragma once

#include "cth/coro/scheduler.hpp"
#include "cth/coro/context.hpp"
#include "cth/coro/awaiters/base/wait_awaiter_base.hpp"

#include <cth/os/wait.hpp>

#include <coroutine>


namespace cth::co {
struct raw_native_handle_awaiter {
co::scheduler const& scheduler;
/**
* waits until the handle is signaled, resumes the caller on the context
* @details a failed wait throws in the caller
*/
struct raw_native_handle_awaiter : wait_awaiter_base {
constexpr raw_native_handle_awaiter(co::context const& ctx, os::native_handle_t h) : context{ctx}, handle{h} {}

co::context const& context;
os::native_handle_t handle;

[[nodiscard]] bool await_ready() const { return os::check_ready(handle); }
void await_suspend(std::coroutine_handle<> h) const {
scheduler.await(handle, [h] { h.resume(); });
}
static constexpr void await_resume() {}
void await_suspend(std::coroutine_handle<> h) { context.await(handle, resume(h)); }
};
}
Loading
Loading