diff --git a/docs/.gitignore b/docs/.gitignore index 378eac2..7294be0 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ build +book diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index b1c8375..07b41b1 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -23,6 +23,10 @@ - [run and run_forever](headers/basic/run.md) - [detached](headers/basic/detached.md) - [spawn](headers/basic/spawn.md) + - [Waitable](headers/basic/waitable.md) + - [Sender](headers/basic/sender.md) + - [Operation](headers/basic/operation.md) + - [Receives](headers/basic/receives.md) - [async/result.hpp](headers/result.md) - [async/oneshot.hpp](headers/oneshot-event.md) - [async/wait-group.hpp](headers/wait-group.md) diff --git a/docs/src/headers/basic/operation.md b/docs/src/headers/basic/operation.md new file mode 100644 index 0000000..a347895 --- /dev/null +++ b/docs/src/headers/basic/operation.md @@ -0,0 +1,41 @@ +# `concept Operation` + +The `Operation` concept holds all the requirements for an +[operation](/sender-receiver.md). + +## Prototype + +```cpp +template +concept Operation = ...; +``` + +### Requirements + +`T` can be started via the [start\_inline](/headers/execution.md) CPO. + +## Examples + +```cpp +template +struct write_operation { + write_operation(write_sender s, Receiver r) + : req_{}, handle_{s.handle}, bufs_{s.bufs}, nbufs_{s.nbufs}, r_{std::move(r)} { } + + write_operation(const write_operation &) = delete; + write_operation &operator=(const write_operation &) = delete; + write_operation(write_operation &&) = delete; + write_operation &operator=(write_operation &&) = delete; + + bool start_inline() { /* omitted for brevity */ } + +private: + uv_write_t req_; + uv_stream_t *handle_; + const uv_buf_t *bufs_; + size_t nbufs_; + + Receiver r_; +}; +static_assert(async::Operation>); +``` diff --git a/docs/src/headers/basic/receives.md b/docs/src/headers/basic/receives.md new file mode 100644 index 0000000..4529127 --- /dev/null +++ b/docs/src/headers/basic/receives.md @@ -0,0 +1,42 @@ +# `concept Receives` + +The `Receives` concept holds all the requirements for a +[receiver](/sender-receiver.md) that can receive a `T` value (or none, when `T` +is `void`). + +## Prototype + +```cpp +template +concept Receives = ...; +``` + +### Requirements + +A `set_value_inline` and `set_value_noinline` members, which can be called with +a `T&&` value, or no parameters, if `T` is `void`. + +## Examples + +```cpp +struct discard_receiver { + template + void set_value_inline(T) { + assert(std::is_constant_evaluated()); + } + void set_value_inline() { + assert(std::is_constant_evaluated()); + } + + template + void set_value_noinline(T) { + assert(std::is_constant_evaluated()); + } + void set_value_noinline() { + assert(std::is_constant_evaluated()); + } +}; +static_assert(async::Receives); +static_assert(async::Receives); +static_assert(async::Receives); +``` diff --git a/docs/src/headers/basic/run.md b/docs/src/headers/basic/run.md index d9eed34..1893c88 100644 --- a/docs/src/headers/basic/run.md +++ b/docs/src/headers/basic/run.md @@ -7,13 +7,13 @@ via the IO service. ## Prototype ```cpp -template +template void run_forever(IoService ios); // (1) template Sender::value_type run(Sender s); // (2) -template +template Sender::value_type run(Sender s, IoService ios); // (3) ``` @@ -24,11 +24,13 @@ inline as there's no way to wait for it to complete. ### Requirements -`IoService` is an [IO service](../../io-service.md), and `Sender` is a sender. +`IoService` is an [IO service](../../io-service.md), and must be a +[Waitable](./waitable.md), and `Sender` is a sender. ### Arguments - - `IoService` - the IO service to use to wait for completion. + - `IoService` - the IO service to use to wait for completion. Must fulfill the + [Waitable](./waitable.md) concept. - `Sender` - the sender to start. ### Return value diff --git a/docs/src/headers/basic/sender.md b/docs/src/headers/basic/sender.md new file mode 100644 index 0000000..d5e678c --- /dev/null +++ b/docs/src/headers/basic/sender.md @@ -0,0 +1,35 @@ +# `concept Sender` + +The `Sender` concept holds all the requirements for a +[sender](/sender-receiver.md). + +## Prototype + +```cpp +template +concept Sender = ...; +``` + +### Requirements + +`T` has a `value_type`, is move constructible, and can be +[connected](/headers/execution.md). + +## Examples + +```cpp +struct [[nodiscard]] write_sender { + using value_type = int; // Status code + + uv_stream_t *handle; + const uv_buf_t *bufs; + size_t nbufs; +}; + +/* operation omitted for brevity */ +template +/*operation*/ connect(write_sender s, Receiver r) { + return {s, std::move(r)}; +} +static_assert(async::Sender); +``` diff --git a/docs/src/headers/basic/waitable.md b/docs/src/headers/basic/waitable.md new file mode 100644 index 0000000..e45652a --- /dev/null +++ b/docs/src/headers/basic/waitable.md @@ -0,0 +1,33 @@ +# `concept Waitable` + +The `Waitable` concept holds all the requirements for an [IO +service](/io-service.md). Presently, this is only a `wait()` method. + +## Prototype + +```cpp +template +concept Waitable = ...; +``` + +### Requirements + +`T` provides a instance wait method that can be called on a value. + +## Examples + +```cpp +struct io_service { +/** \pre loop must still be alive */ +void wait() { + auto loop = m_loop.lock(); + assert(loop); + loop->wait(); +} +private: +friend struct event; +io_service(std::weak_ptr e) : m_loop { std::move(e) } {} +std::weak_ptr m_loop; +}; +static_assert(async::Waitable); +``` diff --git a/docs/src/headers/wait-group.md b/docs/src/headers/wait-group.md new file mode 100644 index 0000000..6a02104 --- /dev/null +++ b/docs/src/headers/wait-group.md @@ -0,0 +1 @@ +# async/wait-group.hpp diff --git a/docs/src/io-service.md b/docs/src/io-service.md index 48ec3c6..4ed2306 100644 --- a/docs/src/io-service.md +++ b/docs/src/io-service.md @@ -7,6 +7,8 @@ The IO service must provide one method: `void wait()`. This method is called whe there is no more work to do currently. It waits for any event to happen, and wakes up the appropriate coroutine/operation which awaited the event. +See also: the [Waitable](/headers/basic/waitable.md) concept. + **Note:** `async::run` and `async::run_forever` (see [here](headers/basic/run.md#prototype)) take the IO service by value, not by reference. diff --git a/include/async/algorithm.hpp b/include/async/algorithm.hpp index a0631e2..e95b6a6 100644 --- a/include/async/algorithm.hpp +++ b/include/async/algorithm.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -9,7 +10,7 @@ namespace async { -template +template Receiver> struct connect_helper { using operation = execution::operation_t; @@ -21,7 +22,7 @@ struct connect_helper { Receiver r; }; -template +template Receiver> connect_helper make_connect_helper(Sender s, Receiver r) { return {std::move(s), std::move(r)}; } @@ -58,7 +59,7 @@ template struct [[nodiscard]] invocable_sender { using value_type = std::invoke_result_t; - template + template R> invocable_operation connect(R r) { return {std::move(f), std::move(r)}; } @@ -141,7 +142,7 @@ template struct [[nodiscard]] transform_sender; template -requires (!std::is_same_v) +requires (!std::same_as) struct [[nodiscard]] transform_sender { using value_type = std::invoke_result_t; @@ -160,11 +161,11 @@ struct [[nodiscard]] transform_sender { }; template -requires std::is_same_v +requires std::same_as struct [[nodiscard]] transform_sender { using value_type = std::invoke_result_t; - template + template Receiver> friend auto connect(transform_sender s, Receiver dr) { return execution::connect(std::move(s.ds), void_transform_receiver{std::move(dr), std::move(s.f)}); @@ -233,7 +234,7 @@ struct [[nodiscard]] ite_sender { ite_sender(C cond, ST then_s, SE else_s) : cond_{std::move(cond)}, then_s_{std::move(then_s)}, else_s_{std::move(else_s)} { } - template + template R> ite_operation connect(R dr) { return {std::move(cond_), std::move(then_s_), std::move(else_s_), std::move(dr)}; } @@ -248,7 +249,8 @@ struct [[nodiscard]] ite_sender { SE else_s_; }; -template +template C, Sender ST, Sender SE> +requires std::same_as ite_sender ite(C cond, ST then_s, SE else_s) { return {std::move(cond), std::move(then_s), std::move(else_s)}; } @@ -325,7 +327,7 @@ struct repeat_while_sender { return {std::move(*this)}; } - template + template R> repeat_while_operation connect(R receiver) { return {std::move(cond), std::move(factory), std::move(receiver)}; } @@ -335,6 +337,10 @@ struct repeat_while_sender { }; template +requires std::move_constructible && requires (C c, SF sf) { + { c() } -> std::convertible_to; + { sf() } -> Sender; +} repeat_while_sender repeat_while(C cond, SF factory) { return {std::move(cond), std::move(factory)}; } @@ -350,7 +356,7 @@ template struct race_and_cancel_sender { using value_type = void; - template + template Receiver> friend race_and_cancel_operation, std::index_sequence_for> connect(race_and_cancel_sender s, Receiver r) { @@ -449,7 +455,8 @@ operator co_await(race_and_cancel_sender s) { return {std::move(s)}; } -template +template... Functors> +requires ((Sender>) && ...) race_and_cancel_sender race_and_cancel(Functors... fs) { return {{std::move(fs)...}}; } @@ -494,7 +501,7 @@ struct [[nodiscard]] let_sender { using imm_type = std::invoke_result_t; using value_type = typename std::invoke_result_t>::value_type; - template + template Receiver> friend let_operation connect(let_sender s, Receiver r) { return {std::move(s.pred), std::move(s.func), std::move(r)}; @@ -510,7 +517,10 @@ operator co_await(let_sender s) { return {std::move(s)}; } -template +template Pred, typename Func> +requires requires (Func func, Pred pred) { + func(std::declval>()); +} let_sender let(Pred pred, Func func) { return {std::move(pred), std::move(func)}; } @@ -684,7 +694,7 @@ struct [[nodiscard]] sequence_operation { frg::aligned_storage box_; }; -template requires (sizeof...(Senders) > 0) +template requires (sizeof...(Senders) > 0) struct [[nodiscard]] sequence_sender { using value_type = typename std::tuple_element_t>::value_type; @@ -697,12 +707,12 @@ struct [[nodiscard]] sequence_sender { frg::tuple senders; }; -template requires (sizeof...(Senders) > 0) +template requires (sizeof...(Senders) > 0) sequence_sender sequence(Senders ...senders) { return {frg::tuple{std::move(senders)...}}; } -template +template sender_awaiter, typename sequence_sender::value_type> operator co_await(sequence_sender s) { return {std::move(s)}; @@ -777,7 +787,7 @@ template requires (sizeof...(Senders) > 0) struct [[nodiscard]] when_all_sender { using value_type = void; - template + template Receiver> friend when_all_operation connect(when_all_sender s, Receiver r) { return {std::move(s.senders), std::move(r)}; @@ -786,7 +796,8 @@ struct [[nodiscard]] when_all_sender { frg::tuple senders; }; -template requires (sizeof...(Senders) > 0) +template +requires (sizeof...(Senders) > 0) when_all_sender when_all(Senders ...senders) { return {frg::tuple{std::move(senders)...}}; } diff --git a/include/async/basic.hpp b/include/async/basic.hpp index 2caf4b6..4313839 100644 --- a/include/async/basic.hpp +++ b/include/async/basic.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -36,6 +37,57 @@ namespace corons = std::experimental; #endif namespace async { +template +concept Receives = std::movable +&& (std::same_as ? +requires(T t) { + { t.set_value_inline() } -> std::same_as; + { t.set_value_noinline() } -> std::same_as; +} +: requires(T t) { + { t.set_value_inline(std::declval()) } -> std::same_as; + { t.set_value_noinline(std::declval()) } -> std::same_as; +}); + +namespace helpers { +template +struct dummy_receiver { + template + requires (!std::same_as) + void set_value_inline(T) { + assert(std::is_constant_evaluated()); + } + void set_value_inline() { + assert(std::is_constant_evaluated()); + } + + template + requires (!std::same_as) + void set_value_noinline(T) { + assert(std::is_constant_evaluated()); + } + void set_value_noinline() { + assert(std::is_constant_evaluated()); + } +}; +static_assert(Receives, void>); +static_assert(Receives, int>); +} /* namespace helpers */ + +template +concept Operation = requires(T &t) { + { execution::start_inline(t) } -> std::same_as; +}; + +/* We require move constructible, rather than movable, since lambdas can be + * move constructible but not movable + */ +template +concept Sender = std::move_constructible && requires(T t) { + typename T::value_type; + { execution::connect(std::move(t), helpers::dummy_receiver<[]{}>{}) } + -> Operation; +}; template requires requires(E &&e) { operator co_await(std::forward(e)); } @@ -68,6 +120,9 @@ enum class maybe_awaited { // sender_awaiter template. // ---------------------------------------------------------------------------- +/* we can't declare S a sender here, since, if we do, it'd be impossible to + * declare a member co_await that returns a sender_awaiter + */ template struct [[nodiscard]] sender_awaiter { private: @@ -156,11 +211,12 @@ struct [[nodiscard]] sender_awaiter { template struct any_receiver { template + requires ( + std::is_trivially_copyable_v + && sizeof(R) <= sizeof(void *) + && alignof(R) <= alignof(void *) + ) any_receiver(R receiver) { - static_assert(std::is_trivially_copyable_v); - static_assert(sizeof(R) <= sizeof(void *)); - static_assert(alignof(R) <= alignof(void *)); - new (stor_) R(receiver); set_value_fptr_ = [] (void *p, T value) { auto *rp = static_cast(p); @@ -184,8 +240,12 @@ struct any_receiver { template<> struct any_receiver { template + requires ( + std::is_trivially_copyable_v + && sizeof(R) <= sizeof(void *) + && alignof(R) <= alignof(void *) + ) any_receiver(R receiver) { - static_assert(std::is_trivially_copyable_v); new (stor_) R(receiver); set_value_fptr_ = [] (void *p) { auto *rp = static_cast(p); @@ -227,10 +287,13 @@ struct callback { callback() : _function(nullptr) { } - template::value - && std::is_trivially_destructible::value>> + template + requires ( + sizeof(F) <= sizeof(void*) + && alignof(F) <= alignof(void*) + && std::is_trivially_copy_constructible_v + && std::is_trivially_destructible_v + ) callback(F functor) : _function(&invoke) { new (&_object) F{std::move(functor)}; @@ -315,16 +378,21 @@ struct run_queue { // Top-level execution functions. // ---------------------------------------------------------------------------- -template +template +concept Waitable = requires (T t) { + t.wait(); +}; + +template void run_forever(IoService ios) { while(true) { ios.wait(); } } -template -std::enable_if_t, void> -run(Sender s) { +template +requires std::same_as +void run(Sender s) { struct receiver { void set_value_inline() { } @@ -338,10 +406,9 @@ run(Sender s) { platform::panic("libasync: Operation hasn't completed and we don't know how to wait"); } -template -std::enable_if_t, - typename Sender::value_type> -run(Sender s) { +template +requires (!std::same_as) +typename Sender::value_type run(Sender s) { struct state { frg::optional value; }; @@ -371,9 +438,9 @@ run(Sender s) { platform::panic("libasync: Operation hasn't completed and we don't know how to wait"); } -template -std::enable_if_t, void> -run(Sender s, IoService ios) { +template +requires std::same_as +void run(Sender s, IoService ios) { struct state { bool done = false; }; @@ -405,10 +472,9 @@ run(Sender s, IoService ios) { } } -template -std::enable_if_t, - typename Sender::value_type> -run(Sender s, IoService ios) { +template +requires (!std::same_as) +typename Sender::value_type run(Sender s, IoService ios) { struct state { bool done = false; frg::optional value; @@ -532,12 +598,12 @@ void detach_with_allocator(Allocator allocator, S sender) { detach_with_allocator(std::move(allocator), std::move(sender), [] { }); } -template +template void detach(S sender) { return detach_with_allocator(frg::stl_allocator{}, std::move(sender)); } -template +template void detach(S sender, Cont continuation) { return detach_with_allocator(frg::stl_allocator{}, std::move(sender), std::move(continuation)); } diff --git a/include/async/execution.hpp b/include/async/execution.hpp index 25d31a6..528bd3b 100644 --- a/include/async/execution.hpp +++ b/include/async/execution.hpp @@ -2,154 +2,125 @@ #include #include +#include -namespace async { - -// Detection pattern boilerplate. - -template -using void_t = void; - -template -constexpr bool dependent_false_t = false; - -template