diff --git a/include/unifex/allocate.hpp b/include/unifex/allocate.hpp index d1fffe320..ab61277e0 100644 --- a/include/unifex/allocate.hpp +++ b/include/unifex/allocate.hpp @@ -93,6 +93,8 @@ namespace _alloc { static constexpr bool sends_done = sender_traits::sends_done; + static constexpr blocking_kind blocking = sender_traits::blocking; + template(typename Self, typename Receiver) (requires same_as, type> AND receiver) @@ -106,8 +108,8 @@ namespace _alloc { static_cast(s).sender_, (Receiver &&) r}; } - friend constexpr auto tag_invoke(tag_t, const type& self) noexcept { - return blocking(self.sender_); + friend constexpr blocking_kind tag_invoke(tag_t, const type& self) noexcept { + return unifex::blocking(self.sender_); } Sender sender_; diff --git a/include/unifex/async_manual_reset_event.hpp b/include/unifex/async_manual_reset_event.hpp index 1b7b59107..0743561fd 100644 --- a/include/unifex/async_manual_reset_event.hpp +++ b/include/unifex/async_manual_reset_event.hpp @@ -16,6 +16,8 @@ #pragma once #include + +#include #include #include #include @@ -54,6 +56,8 @@ struct _sender { static constexpr bool sends_done = false; + static constexpr blocking_kind blocking = blocking_kind::never; + explicit _sender(async_manual_reset_event& evt) noexcept : evt_(&evt) {} diff --git a/include/unifex/async_mutex.hpp b/include/unifex/async_mutex.hpp index 5696233f4..66f5ccb38 100644 --- a/include/unifex/async_mutex.hpp +++ b/include/unifex/async_mutex.hpp @@ -60,6 +60,9 @@ class async_mutex { static constexpr bool sends_done = false; + // we complete inline if we manage to grab the lock immediately + static constexpr blocking_kind blocking = blocking_kind::maybe; + lock_sender(const lock_sender &) = delete; lock_sender(lock_sender &&) = default; diff --git a/include/unifex/async_trace.hpp b/include/unifex/async_trace.hpp index 75d078455..3d39e5cfe 100644 --- a/include/unifex/async_trace.hpp +++ b/include/unifex/async_trace.hpp @@ -98,14 +98,12 @@ namespace _async_trace { static constexpr bool sends_done = false; + static constexpr blocking_kind blocking = blocking_kind::always_inline; + template operation connect(Receiver&& r) const& { return operation{(Receiver &&) r}; } - - friend auto tag_invoke(tag_t, const sender&) noexcept { - return blocking_kind::always_inline; - } }; } // namespace _async_trace using async_trace_sender = _async_trace::sender; diff --git a/include/unifex/at_coroutine_exit.hpp b/include/unifex/at_coroutine_exit.hpp index 267832168..ec74563e3 100644 --- a/include/unifex/at_coroutine_exit.hpp +++ b/include/unifex/at_coroutine_exit.hpp @@ -188,6 +188,8 @@ struct _die_on_done { static constexpr bool sends_done = false; + static constexpr blocking_kind blocking = sender_traits::blocking; + template (typename Receiver) (requires sender_to>) auto connect(Receiver&& rec) && @@ -288,6 +290,7 @@ struct [[nodiscard]] _cleanup_task { return std::move(std::exchange(continuation_, {}).promise().args_); } + // TODO: how do we address always-inline awaitables friend constexpr auto tag_invoke(tag_t, const _cleanup_task&) noexcept { return blocking_kind::always_inline; } diff --git a/include/unifex/blocking.hpp b/include/unifex/blocking.hpp index 6e3dd95af..f149a15bc 100644 --- a/include/unifex/blocking.hpp +++ b/include/unifex/blocking.hpp @@ -23,10 +23,19 @@ namespace unifex { namespace _block { -enum class _enum { +enum class _enum : unsigned char { + // Caller guarantees that the receiver will be called inline on the + // current thread that called .start() before .start() returns. + always_inline = 0, + + // Guarantees that the receiver will be called strongly-happens-before + // .start() returns. Does not guarantee the call to the receiver happens + // on the same thread that called .start(), however. + always, + // No guarantees about the timing and context on which the receiver will // be called. - maybe = 0, + maybe, // Always completes asynchronously. // Guarantees that the receiver will not be called on the current thread @@ -34,16 +43,23 @@ enum class _enum { // before .start() returns, however, or may be called on the current thread // some time after .start() returns. never, +}; - // Guarantees that the receiver will be called strongly-happens-before - // .start() returns. Does not guarantee the call to the receiver happens - // on the same thread that called .start(), however. - always, +constexpr bool operator<(_enum lhs, _enum rhs) noexcept { + return static_cast(lhs) < static_cast(rhs); +} - // Caller guarantees that the receiver will be called inline on the - // current thread that called .start() before .start() returns. - always_inline -}; +constexpr bool operator>(_enum lhs, _enum rhs) noexcept { + return rhs < lhs; +} + +constexpr bool operator<=(_enum lhs, _enum rhs) noexcept { + return !(lhs > rhs); +} + +constexpr bool operator>=(_enum lhs, _enum rhs) noexcept { + return !(lhs < rhs); +} struct blocking_kind { template <_enum Kind> @@ -68,22 +84,23 @@ struct blocking_kind { return value; } - friend constexpr bool operator==(blocking_kind a, blocking_kind b) noexcept { - return a.value == b.value; - } - - friend constexpr bool operator!=(blocking_kind a, blocking_kind b) noexcept { - return a.value != b.value; - } - static constexpr constant<_enum::maybe> maybe {}; static constexpr constant<_enum::never> never {}; static constexpr constant<_enum::always> always {}; static constexpr constant<_enum::always_inline> always_inline {}; - _enum value{}; + _enum value{_enum::maybe}; }; +template +struct _has_blocking : std::false_type {}; + +template +struct _has_blocking< + Sender, + std::void_t> + : std::true_type {}; + struct _fn { template(typename Sender) (requires tag_invocable<_fn, const Sender&>) @@ -95,30 +112,18 @@ struct _fn { template(typename Sender) (requires (!tag_invocable<_fn, const Sender&>)) constexpr auto operator()(const Sender&) const noexcept { - return blocking_kind::maybe; + if constexpr (_has_blocking::value) { + return blocking_kind::constant{}; + } + else { + return blocking_kind::maybe; + } } }; -namespace _cfn { - template <_enum Kind> - static constexpr auto _kind(blocking_kind::constant kind) noexcept { - return kind; - } - static constexpr auto _kind(blocking_kind) noexcept { - return blocking_kind::maybe; - } - - template - constexpr auto cblocking() noexcept { - using blocking_t = remove_cvref_t; - return _cfn::_kind(blocking_t{}); - } -} - } // namespace _block inline constexpr _block::_fn blocking {}; -using _block::_cfn::cblocking; using _block::blocking_kind; } // namespace unifex diff --git a/include/unifex/bulk_join.hpp b/include/unifex/bulk_join.hpp index 0848ea303..288603a17 100644 --- a/include/unifex/bulk_join.hpp +++ b/include/unifex/bulk_join.hpp @@ -101,6 +101,8 @@ class _join_sender::type { static constexpr bool sends_done = sender_traits::sends_done; + static constexpr blocking_kind blocking = sender_traits::blocking; + template explicit type(Source2&& s) noexcept(std::is_nothrow_constructible_v) @@ -122,6 +124,10 @@ class _join_sender::type { join_receiver>{static_cast(r)}); } + friend constexpr blocking_kind tag_invoke(tag_t, const type& s) noexcept { + return unifex::blocking(s.source_); + } + private: Source source_; }; diff --git a/include/unifex/bulk_schedule.hpp b/include/unifex/bulk_schedule.hpp index ff98e813d..d7cbb4004 100644 --- a/include/unifex/bulk_schedule.hpp +++ b/include/unifex/bulk_schedule.hpp @@ -166,6 +166,8 @@ class _default_sender::type { static constexpr bool sends_done = true; + static constexpr blocking_kind blocking = sender_traits::blocking; + template explicit type(Scheduler2&& s, Integral count) : scheduler_(static_cast(s)) @@ -185,6 +187,10 @@ class _default_sender::type { static_cast(r)}); } + friend blocking_kind tag_invoke(tag_t, const type& self) noexcept { + return unifex::blocking(self.scheduler_); + } + private: Scheduler scheduler_; Integral count_; diff --git a/include/unifex/bulk_transform.hpp b/include/unifex/bulk_transform.hpp index 0d0c799eb..faed5cb60 100644 --- a/include/unifex/bulk_transform.hpp +++ b/include/unifex/bulk_transform.hpp @@ -162,6 +162,8 @@ class _tfx_sender::type { static constexpr bool sends_done = sender_traits::sends_done; + static constexpr blocking_kind blocking = sender_traits::blocking; + template explicit type(Source2&& source, Func2&& func, Policy policy) : source_((Source2&&)source) @@ -189,6 +191,10 @@ class _tfx_sender::type { static_cast(r)}); } + friend constexpr blocking_kind tag_invoke(tag_t, const type& s) noexcept { + return unifex::blocking(s.source_); + } + private: UNIFEX_NO_UNIQUE_ADDRESS Source source_; UNIFEX_NO_UNIQUE_ADDRESS Func func_; diff --git a/include/unifex/connect_awaitable.hpp b/include/unifex/connect_awaitable.hpp index 765c1ee17..5d64b1b36 100644 --- a/include/unifex/connect_awaitable.hpp +++ b/include/unifex/connect_awaitable.hpp @@ -235,6 +235,7 @@ namespace _as_sender { return unifex::connect_awaitable(((type&&) t).awaitable_, (Receiver&&) r); } + // TODO: how do we make this property statically discoverable? friend constexpr auto tag_invoke(tag_t, const type& t) noexcept { return unifex::blocking(t.awaitable_); } @@ -267,6 +268,7 @@ namespace _as_sender { return unifex::connect_awaitable(((type&&) t).awaitable_, (Receiver&&) r); } + // TODO: how do we make this property statically discoverable? friend constexpr auto tag_invoke(tag_t, const type& t) noexcept { return unifex::blocking(t.awaitable_); } diff --git a/include/unifex/create.hpp b/include/unifex/create.hpp index 973f93539..b6a80bc1b 100644 --- a/include/unifex/create.hpp +++ b/include/unifex/create.hpp @@ -90,6 +90,10 @@ struct _snd_base { static constexpr bool sends_done = true; + // there's no way to know; maybe create() should have a way for the user to + // specify + static constexpr blocking_kind blocking = blocking_kind::maybe; + template (typename Self, typename Receiver) (requires derived_from, type> AND constructible_from> AND @@ -157,7 +161,7 @@ struct _fn { * \fn template auto create(auto fn [, auto ctx]) * \brief A utility for building a sender-based API out of a C-style API that * accepts a void* context and a function pointer continuation. - * + * * \em Example: * \code * // A void-returning C-style async API that accepts a context and a continuation: diff --git a/include/unifex/dematerialize.hpp b/include/unifex/dematerialize.hpp index 59a23051b..b9e939181 100644 --- a/include/unifex/dematerialize.hpp +++ b/include/unifex/dematerialize.hpp @@ -163,6 +163,8 @@ namespace _demat { static constexpr bool sends_done = sender_traits::sends_done; + static constexpr blocking_kind blocking = sender_traits::blocking; + template explicit type(Source2&& source) noexcept(std::is_nothrow_constructible_v) @@ -180,8 +182,9 @@ namespace _demat { receiver_t{static_cast(r)}); } - friend constexpr auto tag_invoke(tag_t, const type& self) noexcept { - return blocking(self.source_); + friend constexpr blocking_kind + tag_invoke(tag_t, const type& self) noexcept { + return unifex::blocking(self.source_); } private: Source source_; diff --git a/include/unifex/detach_on_cancel.hpp b/include/unifex/detach_on_cancel.hpp index 9e2940b4d..386ad2f1d 100644 --- a/include/unifex/detach_on_cancel.hpp +++ b/include/unifex/detach_on_cancel.hpp @@ -20,6 +20,8 @@ #include #include #include + +#include #include #include @@ -204,21 +206,16 @@ struct _sender::type { static constexpr bool sends_done = true; - friend constexpr auto tag_invoke(tag_t, const type& sender) noexcept { - if constexpr (same_as) { - // the sender returns a runtime-determined blocking_kind - blocking_kind blockValue = blocking(sender.upstreamSender_); - if (blockValue == blocking_kind::never) { - blockValue = blocking_kind::maybe; - } - return blockValue; - } else if constexpr (blocking_kind::never == cblocking()) { - // the sender always returns never - return blocking_kind::maybe; - } else { - return cblocking(); - } + // We will complete inline if started with a stop token that has had stop + // requested. If Sender is maybe or never then we're maybe overall; if Sender is + // always then we can report always; otherwise Sender is always_inline and we + // can report that. + static constexpr blocking_kind blocking = + std::min(blocking_kind::maybe(), sender_traits::blocking()); + + friend constexpr blocking_kind tag_invoke(tag_t, const type& sender) noexcept { + blocking_kind other{blocking(sender)}; + return std::min(blocking_kind::maybe(), other()); } template diff --git a/include/unifex/finally.hpp b/include/unifex/finally.hpp index 5b973be9d..7228b1b89 100644 --- a/include/unifex/finally.hpp +++ b/include/unifex/finally.hpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -72,26 +73,6 @@ namespace unifex Receiver, std::decay_t...>::type; - constexpr blocking_kind _blocking_kind( - blocking_kind source, - blocking_kind completion) noexcept { - if (source == blocking_kind::never || completion == blocking_kind::never) { - return blocking_kind::never; - } else if ( - source == blocking_kind::always_inline && - completion == blocking_kind::always_inline) { - return blocking_kind::always_inline; - } else if ( - (source == blocking_kind::always_inline || - source == blocking_kind::always) && - (completion == blocking_kind::always_inline || - completion == blocking_kind::always)) { - return blocking_kind::always; - } else { - return blocking_kind::maybe; - } - } - template < typename SourceSender, typename CompletionSender, @@ -684,6 +665,10 @@ namespace unifex sender_traits::sends_done || sender_traits::sends_done; + static constexpr blocking_kind blocking = std::max( + sender_traits::blocking(), + sender_traits::blocking()); + template explicit type( SourceSender2&& source, CompletionSender2&& completion) @@ -724,23 +709,10 @@ namespace unifex static_cast(r)}; } - friend constexpr auto tag_invoke(tag_t, const type& self) noexcept { - if constexpr ( - blocking_kind::never == cblocking() || - blocking_kind::never == cblocking()) { - return blocking_kind::never; - } else if constexpr ( - blocking_kind::maybe != cblocking() && - blocking_kind::maybe != cblocking()) { - return blocking_kind::constant< - _final::_blocking_kind( - cblocking(), - cblocking())>{}; - } else { - return _final::_blocking_kind( - blocking(self.source_), - blocking(self.completion_)); - } + friend constexpr blocking_kind tag_invoke(tag_t, const type& self) noexcept { + blocking_kind source = blocking(self.source_); + blocking_kind completion = blocking(self.completion_); + return std::max(source(), completion()); } SourceSender source_; diff --git a/include/unifex/find_if.hpp b/include/unifex/find_if.hpp index 28a4382dd..0b5f9a91a 100644 --- a/include/unifex/find_if.hpp +++ b/include/unifex/find_if.hpp @@ -383,8 +383,9 @@ struct _sender::type { template using receiver_type = receiver_t; + // TODO: it's unclear whether this implementation is accurate friend constexpr auto tag_invoke(tag_t, const type& sender) { - return blocking(sender.pred_); + return unifex::blocking(sender.pred_); } template(typename Sender, typename Receiver) diff --git a/include/unifex/get_stop_token.hpp b/include/unifex/get_stop_token.hpp index e402d33c0..af02eebeb 100644 --- a/include/unifex/get_stop_token.hpp +++ b/include/unifex/get_stop_token.hpp @@ -44,6 +44,7 @@ namespace _get_stop_token { return (StopToken&&) stoken_; } + // TODO: how do we express always-inline awaitables? friend constexpr auto tag_invoke(tag_t, const _awaiter&) noexcept { return blocking_kind::always_inline; } diff --git a/include/unifex/indexed_for.hpp b/include/unifex/indexed_for.hpp index 6fe60a041..eb481ee00 100644 --- a/include/unifex/indexed_for.hpp +++ b/include/unifex/indexed_for.hpp @@ -147,10 +147,12 @@ struct _sender::type { static constexpr bool sends_done = sender_traits::sends_done; - friend constexpr auto tag_invoke( + static constexpr blocking_kind blocking = sender_traits::blocking; + + friend constexpr blocking_kind tag_invoke( tag_t, const sender& sender) { - return blocking(sender.pred_); + return unifex::blocking(sender.pred_); } template diff --git a/include/unifex/inline_scheduler.hpp b/include/unifex/inline_scheduler.hpp index 08e510524..ab780b4ef 100644 --- a/include/unifex/inline_scheduler.hpp +++ b/include/unifex/inline_scheduler.hpp @@ -76,11 +76,7 @@ namespace _inline_sched { static constexpr bool sends_done = true; - friend constexpr auto tag_invoke( - tag_t, - const schedule_task&) noexcept { - return blocking_kind::always_inline; - } + static constexpr blocking_kind blocking = blocking_kind::always_inline; template operation connect(Receiver&& receiver) { diff --git a/include/unifex/into_variant.hpp b/include/unifex/into_variant.hpp index a8792ab81..38bf7be11 100644 --- a/include/unifex/into_variant.hpp +++ b/include/unifex/into_variant.hpp @@ -96,17 +96,19 @@ struct _sender::type { static constexpr bool sends_done = sender_traits::sends_done; + static constexpr blocking_kind blocking = sender_traits::blocking; + template using receiver_t = receiver_t>; - friend constexpr auto tag_invoke(tag_t, const type& sender) { - return blocking(sender.pred_); + friend constexpr blocking_kind tag_invoke(tag_t, const type& sender) { + return unifex::blocking(sender.pred_); } template(typename Sender, typename Receiver) (requires same_as, type> AND receiver AND sender_to, receiver_t>>) - friend auto tag_invoke(tag_t, Sender&& s, Receiver&& r) + friend auto tag_invoke(tag_t, Sender&& s, Receiver&& r) noexcept( std::is_nothrow_constructible_v, Receiver> && is_nothrow_connectable_v, receiver_t>>) @@ -127,7 +129,7 @@ namespace _cpo { tag_invocable<_fn, Sender>, meta_tag_invoke_result<_fn>, meta_quote1<_into_variant::sender>>::template apply; - + public: template(typename Sender) (requires tag_invocable<_fn, Sender>) @@ -136,10 +138,10 @@ namespace _cpo { -> _result_t { return unifex::tag_invoke(_fn{}, (Sender &&)(predecessor)); } - + template(typename Sender) (requires(!tag_invocable<_fn, Sender>)) - auto operator()(Sender&& predecessor) const + auto operator()(Sender&& predecessor) const noexcept(std::is_nothrow_constructible_v< _into_variant::sender, Sender>) -> _result_t { diff --git a/include/unifex/just.hpp b/include/unifex/just.hpp index e806cd0e4..b538f7bca 100644 --- a/include/unifex/just.hpp +++ b/include/unifex/just.hpp @@ -78,6 +78,8 @@ class _sender::type { static constexpr bool sends_done = false; + static constexpr auto blocking = blocking_kind::always_inline; + template(typename... Values2) (requires (sizeof...(Values2) == sizeof...(Values)) AND constructible_from, Values2...>) @@ -94,10 +96,6 @@ class _sender::type { -> operation { return {static_cast(that).values_, static_cast(r)}; } - - friend constexpr auto tag_invoke(tag_t, const type&) noexcept { - return blocking_kind::always_inline; - } }; } // namespace _just diff --git a/include/unifex/just_done.hpp b/include/unifex/just_done.hpp index 2f1f02c63..e69afd2a6 100644 --- a/include/unifex/just_done.hpp +++ b/include/unifex/just_done.hpp @@ -59,6 +59,8 @@ class sender { static constexpr bool sends_done = true; + static constexpr auto blocking = blocking_kind::always_inline; + template(typename This, typename Receiver) (requires same_as, sender> AND receiver) @@ -67,10 +69,6 @@ class sender { -> operation { return {static_cast(r)}; } - - friend constexpr auto tag_invoke(tag_t, const sender&) noexcept { - return blocking_kind::always_inline; - } }; } // namespace _just_done diff --git a/include/unifex/just_error.hpp b/include/unifex/just_error.hpp index e152b525b..32d315f1f 100644 --- a/include/unifex/just_error.hpp +++ b/include/unifex/just_error.hpp @@ -70,6 +70,8 @@ class _sender::type { static constexpr bool sends_done = false; + static constexpr auto blocking = blocking_kind::always_inline; + template explicit type(std::in_place_t, Error2&& error) noexcept(std::is_nothrow_constructible_v) @@ -84,10 +86,6 @@ class _sender::type { -> operation { return {static_cast(that).error_, static_cast(r)}; } - - friend constexpr auto tag_invoke(tag_t, const type&) noexcept { - return blocking_kind::always_inline; - } }; } // namespace _just_error diff --git a/include/unifex/just_void_or_done.hpp b/include/unifex/just_void_or_done.hpp index 279f0fa1d..14caf5046 100644 --- a/include/unifex/just_void_or_done.hpp +++ b/include/unifex/just_void_or_done.hpp @@ -64,6 +64,8 @@ struct _sender { static constexpr bool sends_done = true; + static constexpr auto blocking = blocking_kind::always_inline; + // clang-format off UNIFEX_TEMPLATE(typename Receiver) (requires receiver) @@ -72,10 +74,6 @@ struct _sender { return operation{static_cast(r), s.isVoid_}; } // clang-format on - - friend constexpr auto tag_invoke(tag_t, const _sender&) noexcept { - return blocking_kind::always_inline; - } }; inline constexpr struct just_void_or_done_fn { diff --git a/include/unifex/let_done.hpp b/include/unifex/let_done.hpp index ca26d949a..1f5dce742 100644 --- a/include/unifex/let_done.hpp +++ b/include/unifex/let_done.hpp @@ -286,6 +286,12 @@ class _sndr::type { static constexpr bool sends_done = sender_traits::sends_done; + static constexpr blocking_kind blocking = std::max( + sender_traits::blocking(), + std::min( + sender_traits::blocking(), + blocking_kind::maybe())); + template explicit type(Source2&& source, Done2&& done) noexcept( @@ -318,6 +324,11 @@ class _sndr::type { }; } + friend constexpr blocking_kind tag_invoke(tag_t, const type& s) noexcept { + blocking_kind pred = unifex::blocking(s); + return std::max(pred(), std::min(sender_traits::blocking(), blocking_kind::maybe())); + } + private: Source source_; Done done_; diff --git a/include/unifex/let_error.hpp b/include/unifex/let_error.hpp index cbe6bd077..6d3c49337 100644 --- a/include/unifex/let_error.hpp +++ b/include/unifex/let_error.hpp @@ -326,6 +326,14 @@ struct sends_done_impl : std::bool_constant::sends_done> { template using any_sends_done = std::disjunction...>; +template +struct max_blocking_kind { + constexpr _block::_enum operator()() const noexcept { + _block::_enum enums[]{sender_traits::blocking, sender_traits::blocking...}; + return *std::max_element(std::begin(enums), std::end(enums)); + } +}; + template class _sndr::type final { @@ -338,6 +346,9 @@ class _sndr::type final { template using sends_done_impl = any_sends_done...>; + template + using max_successor_blocking = max_blocking_kind...>; + public: template < template @@ -365,6 +376,15 @@ class _sndr::type final { sender_traits::sends_done || sender_error_types_t::value; + // the successors' blocking trait only affects our blocking_kind if the + // predecessor fails so the successors can't raise our blocking_kind past + // maybe + static constexpr blocking_kind blocking = std::max( + sender_traits::blocking(), + std::min( + sender_error_types_t{}(), + blocking_kind::maybe())); + template explicit type(Source2&& source, Func2&& func) noexcept( @@ -397,6 +417,16 @@ class _sndr::type final { }; } + friend constexpr blocking_kind tag_invoke(tag_t, const type& s) noexcept { + // get the runtime blocking_kind for the predecessor + blocking_kind pred = blocking(s.source_); + // we have to go with the static result for the successors since we don't + // know how pred_ will complete + blocking_kind succ = sender_error_types_t{}(); + + return std::max(pred(), std::min(succ(), blocking_kind::maybe())); + } + private: Source source_; Func func_; diff --git a/include/unifex/let_value.hpp b/include/unifex/let_value.hpp index ebaf52a60..0d82d5bf7 100644 --- a/include/unifex/let_value.hpp +++ b/include/unifex/let_value.hpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -281,51 +282,15 @@ struct sends_done_impl : std::bool_constant::sends_done> { template using any_sends_done = std::disjunction...>; -template -struct max_blocking_kind { - constexpr auto operator()() noexcept { return cblocking(); } -}; -template -struct max_blocking_kind { - constexpr auto operator()() noexcept { - constexpr blocking_kind first = cblocking(); - constexpr blocking_kind second = cblocking(); - - if constexpr (first == second) { - return max_blocking_kind{}(); - } else if constexpr ( - first == blocking_kind::always && - second == blocking_kind::always_inline) { - return max_blocking_kind{}(); - } else if constexpr ( - first == blocking_kind::always_inline && - second == blocking_kind::always) { - return max_blocking_kind{}(); - } else { - return blocking_kind::maybe; - } +template +struct max_blocking_kind { + constexpr _block::_enum operator()() const noexcept { + _block::_enum enums[]{sender_traits::blocking, sender_traits::blocking...}; + return *std::max_element(std::begin(enums), std::end(enums)); } }; -constexpr blocking_kind _blocking_kind(blocking_kind source, blocking_kind completion) noexcept { - if (source == blocking_kind::never || completion == blocking_kind::never) { - return blocking_kind::never; - } else if ( - source == blocking_kind::always_inline && - completion == blocking_kind::always_inline) { - return blocking_kind::always_inline; - } else if ( - (source == blocking_kind::always_inline || - source == blocking_kind::always) && - (completion == blocking_kind::always_inline || - completion == blocking_kind::always)) { - return blocking_kind::always; - } else { - return blocking_kind::maybe; - } -} - template class _sender::type { using sender = type; @@ -386,6 +351,12 @@ class _sender::type { sender_traits::sends_done || successor_types::value; + static constexpr blocking_kind blocking = std::max( + sender_traits::blocking(), + std::min( + successor_types<_let_v::max_blocking_kind>{}(), + blocking_kind::maybe())); + public: template explicit type(Predecessor2&& pred, SuccessorFactory2&& func) @@ -404,17 +375,14 @@ class _sender::type { static_cast(receiver)}; } - friend constexpr auto tag_invoke(tag_t, const type&) noexcept { - constexpr blocking_kind succ = successor_types<_let_v::max_blocking_kind>{}(); - if constexpr ( - blocking_kind::never == cblocking() || blocking_kind::never == succ) { - return blocking_kind::never; - } else if constexpr ( - blocking_kind::maybe != cblocking() && blocking_kind::maybe != succ) { - return blocking_kind::constant<_let_v::_blocking_kind(cblocking(), succ)>{}; - } else { - return _let_v::_blocking_kind(cblocking(), succ); - } + friend constexpr blocking_kind tag_invoke(tag_t, const type& sender) noexcept { + // get the runtime blocking_kind for the predecessor + blocking_kind pred = blocking(sender.pred_); + // we have to go with the static result for the successors since we don't + // know how pred_ will complete + blocking_kind succ = successor_types<_let_v::max_blocking_kind>{}(); + + return std::max(pred(), std::min(succ(), blocking_kind::maybe())); } }; diff --git a/include/unifex/let_value_with.hpp b/include/unifex/let_value_with.hpp index 6366b3e47..c833e98a5 100644 --- a/include/unifex/let_value_with.hpp +++ b/include/unifex/let_value_with.hpp @@ -59,6 +59,8 @@ class _sender::type { static constexpr bool sends_done = sender_traits::sends_done; + static constexpr blocking_kind blocking = sender_traits::blocking; + template explicit type(StateFactory2&& stateFactory, SuccessorFactory2&& func) : stateFactory_((StateFactory2&&)stateFactory), diff --git a/include/unifex/let_value_with_stop_source.hpp b/include/unifex/let_value_with_stop_source.hpp index 53e90e517..a780d44cb 100644 --- a/include/unifex/let_value_with_stop_source.hpp +++ b/include/unifex/let_value_with_stop_source.hpp @@ -116,6 +116,8 @@ class _stop_source_sender::type { static constexpr bool sends_done = sender_traits::sends_done; + static constexpr blocking_kind blocking = sender_traits::blocking; + template explicit type(SuccessorFactory2&& func) : func_((SuccessorFactory2&&)func) {} diff --git a/include/unifex/let_value_with_stop_token.hpp b/include/unifex/let_value_with_stop_token.hpp index 2d76909c1..907c43e45 100644 --- a/include/unifex/let_value_with_stop_token.hpp +++ b/include/unifex/let_value_with_stop_token.hpp @@ -143,6 +143,8 @@ class _stop_token_sender::type { static constexpr bool sends_done = sender_traits::sends_done; + static constexpr blocking_kind blocking = sender_traits::blocking; + template explicit type(SuccessorFactory2&& func) noexcept( std::is_nothrow_constructible_v) diff --git a/include/unifex/manual_event_loop.hpp b/include/unifex/manual_event_loop.hpp index b109a7b9d..658671583 100644 --- a/include/unifex/manual_event_loop.hpp +++ b/include/unifex/manual_event_loop.hpp @@ -89,12 +89,6 @@ class context { public: class scheduler { class schedule_task { - friend constexpr auto tag_invoke( - tag_t, - const schedule_task&) noexcept { - return blocking_kind::never; - } - public: template < template class Variant, @@ -106,6 +100,8 @@ class context { static constexpr bool sends_done = true; + static constexpr blocking_kind blocking = blocking_kind::never; + template operation connect(Receiver&& receiver) const { return operation{(Receiver &&) receiver, loop_}; diff --git a/include/unifex/materialize.hpp b/include/unifex/materialize.hpp index 8b06dfa0f..4d71605c6 100644 --- a/include/unifex/materialize.hpp +++ b/include/unifex/materialize.hpp @@ -182,6 +182,8 @@ namespace unifex static constexpr bool sends_done = false; + static constexpr blocking_kind blocking = sender_traits::blocking; + template(typename Source2) (requires constructible_from) explicit type(Source2&& source) noexcept( @@ -201,6 +203,10 @@ namespace unifex receiver_t{static_cast(r)}); } + friend constexpr blocking_kind tag_invoke(tag_t, const type& s) noexcept { + return unifex::blocking(s.source_); + } + private: Source source_; }; diff --git a/include/unifex/never.hpp b/include/unifex/never.hpp index 7dec44e64..d73ef6821 100644 --- a/include/unifex/never.hpp +++ b/include/unifex/never.hpp @@ -84,6 +84,9 @@ struct sender { static constexpr bool sends_done = true; + // we'll complete inline if started with a stopped stop token + static constexpr blocking_kind blocking = blocking_kind::maybe; + template operation connect(Receiver&& receiver) { return operation{(Receiver &&) receiver}; diff --git a/include/unifex/new_thread_context.hpp b/include/unifex/new_thread_context.hpp index 5900f6f1d..aef70f491 100644 --- a/include/unifex/new_thread_context.hpp +++ b/include/unifex/new_thread_context.hpp @@ -76,6 +76,10 @@ class context { static constexpr bool sends_done = true; + // we report failure to construct a thread inline; maybe we should + // terminate in that case and change this to never + static constexpr blocking_kind blocking = blocking_kind::maybe; + explicit schedule_sender(context* ctx) noexcept : context_(ctx) {} diff --git a/include/unifex/range_stream.hpp b/include/unifex/range_stream.hpp index d4627a076..a48010552 100644 --- a/include/unifex/range_stream.hpp +++ b/include/unifex/range_stream.hpp @@ -57,11 +57,7 @@ struct next_sender { static constexpr bool sends_done = true; - friend constexpr auto tag_invoke( - tag_t, - const stream&) noexcept { - return blocking_kind::always_inline; - } + static constexpr blocking_kind blocking = blocking_kind::always_inline; template operation connect(Receiver&& receiver) && { diff --git a/include/unifex/ready_done_sender.hpp b/include/unifex/ready_done_sender.hpp index c4cf41989..5d5329988 100644 --- a/include/unifex/ready_done_sender.hpp +++ b/include/unifex/ready_done_sender.hpp @@ -52,11 +52,7 @@ namespace _ready_done { static constexpr bool sends_done = true; - friend constexpr auto tag_invoke( - tag_t, - const sender&) { - return blocking_kind::always_inline; - } + static constexpr blocking_kind blocking = blocking_kind::always_inline; template operation connect(Receiver&& receiver) const& { diff --git a/include/unifex/repeat_effect_until.hpp b/include/unifex/repeat_effect_until.hpp index a08bc3041..fc9516cb4 100644 --- a/include/unifex/repeat_effect_until.hpp +++ b/include/unifex/repeat_effect_until.hpp @@ -202,6 +202,8 @@ class _sndr::type { static constexpr bool sends_done = true; + static constexpr blocking_kind blocking = sender_traits::blocking; + template explicit type(Source2&& source, Predicate2&& predicate) noexcept( @@ -231,6 +233,10 @@ class _sndr::type { }; } + friend constexpr blocking_kind tag_invoke(tag_t, const type& self) noexcept { + return unifex::blocking(self.source_); + } + private: UNIFEX_NO_UNIQUE_ADDRESS Source source_; UNIFEX_NO_UNIQUE_ADDRESS Predicate predicate_; diff --git a/include/unifex/retry_when.hpp b/include/unifex/retry_when.hpp index e4393c02d..2d03181e7 100644 --- a/include/unifex/retry_when.hpp +++ b/include/unifex/retry_when.hpp @@ -350,6 +350,8 @@ class _sender::type { static constexpr bool sends_done = sender_error_types_t::value; + // TODO: blocking and is_always_scheduler_affine; I don't have the brains to figure them out right now + template explicit type(Source2&& source, Func2&& func) noexcept(std::is_nothrow_constructible_v && diff --git a/include/unifex/scheduler_concepts.hpp b/include/unifex/scheduler_concepts.hpp index 6271ef29a..d2671cefa 100644 --- a/include/unifex/scheduler_concepts.hpp +++ b/include/unifex/scheduler_concepts.hpp @@ -206,6 +206,10 @@ struct sender { static constexpr bool sends_done = true; + // never is very likely, but we don't actually know because it depends on the + // receiver's scheduler's type, which we don't know + static constexpr blocking_kind blocking = blocking_kind::maybe; + template(typename Receiver) (requires receiver) friend auto tag_invoke(tag_t, sender, Receiver &&r) @@ -281,6 +285,10 @@ namespace _schedule_after { static constexpr bool sends_done = true; + // never is very likely, but we don't actually know because it depends on the + // receiver's scheduler's type, which we don't know + static constexpr blocking_kind blocking = blocking_kind::maybe; + explicit type(Duration d) : duration_(d) {} @@ -365,6 +373,10 @@ namespace _schedule_at { static constexpr bool sends_done = true; + // never is very likely, but we don't actually know because it depends on the + // receiver's scheduler's type, which we don't know + static constexpr blocking_kind blocking = blocking_kind::maybe; + explicit type(TimePoint tp) : time_point_(tp) {} @@ -435,6 +447,8 @@ namespace _current { Scheduler await_resume() { return (Scheduler&&) sched_; } + + // TODO: we need to handle always-inline awaitables friend constexpr auto tag_invoke(tag_t, const _awaiter&) noexcept { return blocking_kind::always_inline; } diff --git a/include/unifex/sender_concepts.hpp b/include/unifex/sender_concepts.hpp index ae6bb9777..1d69ec12b 100644 --- a/include/unifex/sender_concepts.hpp +++ b/include/unifex/sender_concepts.hpp @@ -16,6 +16,8 @@ #pragma once #include + +#include #include #include #include @@ -42,12 +44,21 @@ struct sender_traits; /// \cond namespace detail { + using unifex::_block::_has_blocking; + template