/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License Version 2.0 with LLVM Exceptions * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * https://llvm.org/LICENSE.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #if UNIFEX_NO_COROUTINES # error "Coroutine support is required to use " #endif #include #include #include namespace unifex { namespace _await { template struct _sender_task { class type; }; template using sender_task = typename _sender_task::type; template class _sender_task::type { public: struct promise_type { template explicit promise_type(Awaitable&, Receiver& r) noexcept : receiver_(r) {} type get_return_object() noexcept { return type{ coro::coroutine_handle::from_promise( *this)}; } coro::suspend_always initial_suspend() noexcept { return {}; } [[noreturn]] coro::suspend_always final_suspend() noexcept { std::terminate(); } [[noreturn]] void unhandled_exception() noexcept { std::terminate(); } [[noreturn]] void return_void() noexcept { std::terminate(); } coro::coroutine_handle<> unhandled_done() noexcept { unifex::set_done(std::move(receiver_)); return coro::noop_coroutine(); } template auto yield_value(Func&& func) noexcept { struct awaiter { Func&& func_; bool await_ready() noexcept { return false; } void await_suspend(coro::coroutine_handle) { ((Func &&) func_)(); } [[noreturn]] void await_resume() noexcept { std::terminate(); } }; return awaiter{(Func &&) func}; } template auto await_transform(Value&& value) -> decltype(auto) { if constexpr (callable) { return unifex::await_transform(*this, (Value&&)value); } else { return Value((Value &&) value); } } template friend void tag_invoke(tag_t, const promise_type& p, Func&& func) { visit_continuations(p.receiver_, (Func&&)func); } template(typename CPO) (requires is_receiver_query_cpo_v AND is_callable_v) friend auto tag_invoke(CPO cpo, const promise_type& p) noexcept(is_nothrow_callable_v) -> callable_result_t { return cpo(std::as_const(p.receiver_)); } Receiver& receiver_; }; coro::coroutine_handle coro_; explicit type( coro::coroutine_handle coro) noexcept : coro_(coro) {} type(type&& other) noexcept : coro_(std::exchange(other.coro_, {})) {} ~type() { if (coro_) coro_.destroy(); } void start() & noexcept { coro_.resume(); } }; } // namespace _await namespace _await_cpo { template using count_types = std::integral_constant; template struct set_value_applicator { Receiver& receiver_; template void operator()(Values&&... values) { unifex::set_value(std::move(receiver_), (Values&&)values...); } }; inline const struct _fn { private: struct _comma_hack { template friend T&& operator,(T&& t, _comma_hack) noexcept { return (T&&) t; } operator unit() const noexcept { return {}; } }; template static auto connect_impl(Awaitable awaitable, Receiver receiver) -> _await::sender_task { #if !UNIFEX_NO_EXCEPTIONS std::exception_ptr ex; try { #endif // !UNIFEX_NO_EXCEPTIONS using result_type = sender_single_value_result_t; // This is a bit mind bending control-flow wise. // We are first evaluating the co_await expression. // Then the result of that is passed into std::invoke // which curries a reference to the result into another // lambda which is then returned to 'co_yield'. // The 'co_yield' expression then invokes this lambda // after the coroutine is suspended so that it is safe // for the receiver to destroy the coroutine. co_yield [&](result_type&& result) { return [&] { constexpr size_t valueOverloadCount = sender_value_types_t::value; static_assert(valueOverloadCount <= 1); if constexpr (valueOverloadCount == 1) { constexpr size_t valueCount = sender_value_types_t::value; if constexpr (valueCount == 0) { unifex::set_value(std::move(receiver)); } else if constexpr (valueCount == 1) { unifex::set_value(std::move(receiver), static_cast(result)); } else { std::apply(set_value_applicator{receiver}, (result_type&&)result); } } else { // Shouldn't complete with a value if there are no value_types // specified. std::terminate(); } }; // The _comma_hack here makes this well-formed when the co_await // expression has type void. This could potentially run into trouble // if the type of the co_await expression itself overloads operator // comma, but that's pretty unlikely. }((co_await (Awaitable &&)awaitable, _comma_hack{})); #if !UNIFEX_NO_EXCEPTIONS } catch (...) { ex = std::current_exception(); } co_yield[&] { unifex::set_error(std::move(receiver), std::move(ex)); }; #endif // !UNIFEX_NO_EXCEPTIONS } public: template auto operator()(Awaitable&& awaitable, Receiver&& receiver) const -> _await::sender_task> { return connect_impl((Awaitable&&)awaitable, (Receiver&&)receiver); } } connect_awaitable{}; } // namespace _await_cpo using _await_cpo::connect_awaitable; } // namespace unifex #include