/* * 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 #include #include #include #include #include #include #include #include #include namespace unifex { namespace _final { template < typename SourceSender, typename CompletionSender, typename Receiver> struct _op { class type; }; template < typename SourceSender, typename CompletionSender, typename Receiver> using operation = typename _op>::type; template < typename SourceSender, typename CompletionSender, typename Receiver, typename... Values> struct _value_receiver { class type; }; template < typename SourceSender, typename CompletionSender, typename Receiver, typename... Values> using value_receiver = typename _value_receiver< SourceSender, CompletionSender, Receiver, std::decay_t...>::type; template < typename SourceSender, typename CompletionSender, typename Receiver, typename... Values> class _value_receiver::type final { using value_receiver = type; using operation_type = operation; public: explicit type(operation_type* op) noexcept : op_(op) {} type(type&& other) noexcept : op_(std::exchange(other.op_, nullptr)) {} void set_value() && noexcept { auto* const op = op_; using completion_value_op_t = connect_result_t; unifex::deactivate_union_member(op->completionValueOp_); UNIFEX_TRY { // Move the stored values onto the stack so that we can // destroy the ones stored in the operation-state. This // prevents the need to add a big switch to the operation // state destructor to determine which value-tuple type // destructor needs to be run. auto values = [op]() -> std::tuple { scope_guard g{[op]() noexcept { unifex::deactivate_union_member>(op->value_); }}; return static_cast&&>( op->value_.template get>()); } (); std::apply( [&](Values&&... values) { unifex::set_value( static_cast(op->receiver_), static_cast(values)...); }, static_cast&&>(values)); } UNIFEX_CATCH (...) { unifex::set_error( static_cast(op->receiver_), std::current_exception()); } } template void set_error(Error&& error) && noexcept { auto* const op = op_; using completion_value_op_t = connect_result_t; unifex::deactivate_union_member(op->completionValueOp_); // Discard the stored value. unifex::deactivate_union_member>(op->value_); unifex::set_error( static_cast(op->receiver_), static_cast(error)); } void set_done() && noexcept { auto* const op = op_; using completion_value_op_t = connect_result_t; unifex::deactivate_union_member(op->completionValueOp_); // Discard the stored value. unifex::deactivate_union_member>(op->value_); unifex::set_done(static_cast(op->receiver_)); } template(typename CPO, typename R) (requires is_receiver_query_cpo_v AND same_as AND is_callable_v) friend auto tag_invoke( CPO cpo, const R& r) noexcept(is_nothrow_callable_v< CPO, const Receiver&>) -> callable_result_t { return static_cast(cpo)(r.get_receiver()); } template friend void tag_invoke( tag_t, const value_receiver& r, Func&& func) { std::invoke(func, r.get_receiver()); } private: const Receiver& get_receiver() const noexcept { return op_->receiver_; } operation_type* op_; }; template < typename SourceSender, typename CompletionSender, typename Receiver, typename Error> struct _error_receiver { class type; }; template < typename SourceSender, typename CompletionSender, typename Receiver, typename Error> using error_receiver = typename _error_receiver< SourceSender, CompletionSender, Receiver, std::decay_t>::type; template < typename SourceSender, typename CompletionSender, typename Receiver, typename Error> class _error_receiver::type final { using error_receiver = type; using operation_type = operation; public: explicit type(operation_type* op) noexcept : op_(op) {} type(type&& other) noexcept : op_(std::exchange(other.op_, nullptr)) {} void set_value() && noexcept { auto* const op = op_; using completion_error_op_t = connect_result_t; unifex::deactivate_union_member(op->completionErrorOp_); Error errorCopy = static_cast(op->error_.template get()); unifex::deactivate_union_member(op->error_); unifex::set_error( static_cast(op->receiver_), static_cast(errorCopy)); } template(typename OtherError) (requires receiver) void set_error(OtherError otherError) && noexcept { auto* const op = op_; using completion_error_op_t = connect_result_t; unifex::deactivate_union_member(op->completionErrorOp_); // Discard existing stored error from source-sender. unifex::deactivate_union_member(op->error_); unifex::set_error( static_cast(op->receiver_), static_cast(otherError)); } void set_done() && noexcept { auto* const op = op_; using completion_error_op_t = connect_result_t; unifex::deactivate_union_member(op->completionErrorOp_); // Discard existing stored error from source-sender. unifex::deactivate_union_member(op->error_); unifex::set_done(static_cast(op->receiver_)); } template(typename CPO, typename R) (requires is_receiver_query_cpo_v AND same_as AND is_callable_v) friend auto tag_invoke( CPO cpo, const R& r) noexcept(is_nothrow_callable_v< CPO, const Receiver&>) -> callable_result_t { return static_cast(cpo)(r.get_receiver()); } template friend void tag_invoke( tag_t, const error_receiver& r, Func&& func) { std::invoke(func, r.get_receiver()); } private: const Receiver& get_receiver() const noexcept { return op_->receiver_; } operation_type* op_; }; template struct _done_receiver { class type; }; template using done_receiver = typename _done_receiver::type; template class _done_receiver::type final { using done_receiver = type; using operation_type = operation; public: explicit type(operation_type* op) noexcept : op_(op) {} type(type&& other) noexcept : op_(std::exchange(other.op_, nullptr)) {} void set_value() && noexcept { auto* const op = op_; unifex::deactivate_union_member(op->completionDoneOp_); unifex::set_done(static_cast(op->receiver_)); } template(typename Error) (requires receiver) void set_error(Error&& error) && noexcept { auto* const op = op_; unifex::deactivate_union_member(op->completionDoneOp_); unifex::set_error( static_cast(op->receiver_), static_cast(error)); } void set_done() && noexcept { auto* const op = op_; unifex::deactivate_union_member(op->completionDoneOp_); unifex::set_done(static_cast(op->receiver_)); } template(typename CPO, typename R) (requires is_receiver_query_cpo_v AND same_as AND is_callable_v) friend auto tag_invoke( CPO cpo, const R& r) noexcept(is_nothrow_callable_v< CPO, const Receiver&>) -> callable_result_t { return static_cast(cpo)(r.get_receiver()); } template friend void tag_invoke( tag_t, const done_receiver& r, Func&& func) { std::invoke(func, r.get_receiver()); } private: const Receiver& get_receiver() const noexcept { return op_->receiver_; } operation_type* op_; }; template struct _receiver { class type; }; template using receiver_t = typename _receiver::type; template class _receiver::type final { using operation_type = operation; public: explicit type(operation_type* op) noexcept : op_(op) {} type(type&& other) noexcept : op_(std::exchange(other.op_, nullptr)) {} template(typename... Values) (requires receiver_of) void set_value(Values&&... values) && noexcept { auto* const op = op_; UNIFEX_TRY { unifex::activate_union_member...>>( op->value_, static_cast(values)...); } UNIFEX_CATCH (...) { std::move(*this).set_error(std::current_exception()); return; } unifex::deactivate_union_member(op->sourceOp_); UNIFEX_TRY { using value_receiver = value_receiver< SourceSender, CompletionSender, Receiver, Values...>; using completion_value_op_t = connect_result_t; auto& completionOp = unifex::activate_union_member_with( op->completionValueOp_, [&] { return unifex::connect( static_cast(op->completionSender_), value_receiver{op}); }); unifex::start(completionOp); } UNIFEX_CATCH (...) { using decayed_tuple_t = std::tuple...>; unifex::deactivate_union_member(op->value_); unifex::set_error( static_cast(op->receiver_), std::current_exception()); } } template void set_error(Error&& error) && noexcept { static_assert( std::is_nothrow_constructible_v, Error>); auto* const op = op_; unifex::activate_union_member>( op->error_, static_cast(error)); unifex::deactivate_union_member(op->sourceOp_); UNIFEX_TRY { using error_receiver_t = error_receiver< SourceSender, CompletionSender, Receiver, Error>; using completion_error_op_t = connect_result_t; auto& completionOp = unifex::activate_union_member_with( op->completionErrorOp_, [&] { return unifex::connect( static_cast(op->completionSender_), error_receiver_t{op}); }); unifex::start(completionOp); } UNIFEX_CATCH (...) { unifex::deactivate_union_member>(op->error_); unifex::set_error( static_cast(op->receiver_), std::current_exception()); } } void set_done() && noexcept { auto* const op = op_; unifex::deactivate_union_member(op->sourceOp_); UNIFEX_TRY { using done_receiver = done_receiver; auto& completionOp = unifex::activate_union_member_with( op->completionDoneOp_, [&] { return unifex::connect( static_cast(op->completionSender_), done_receiver{op}); }); unifex::start(completionOp); } UNIFEX_CATCH (...) { unifex::set_error( static_cast(op->receiver_), std::current_exception()); } } template(typename CPO, typename R) (requires is_receiver_query_cpo_v AND same_as AND is_callable_v) friend auto tag_invoke(CPO cpo, const R& r) noexcept( is_nothrow_callable_v) -> callable_result_t { return static_cast(cpo)(r.get_receiver()); } template friend void tag_invoke( tag_t, const type& r, Func&& func) { std::invoke(func, r.get_receiver()); } private: const Receiver& get_receiver() const noexcept { return op_->receiver_; } operation_type* op_; }; template < typename SourceSender, typename CompletionSender, typename Receiver> class _op::type { friend receiver_t; friend done_receiver; template < typename SourceSender2, typename CompletionSender2, typename Receiver2, typename... Values> friend struct _value_receiver; template < typename SourceSender2, typename CompletionSender2, typename Receiver2, typename Error> friend struct _error_receiver; template using value_operation = connect_result_t< CompletionSender, value_receiver>; template using error_operation = connect_result_t< CompletionSender, error_receiver>; template using error_operation_union = manual_lifetime_union< error_operation, error_operation...>; using done_operation = connect_result_t< CompletionSender, done_receiver>; template using error_result_union = manual_lifetime_union; public: template explicit type( SourceSender&& sourceSender, CompletionSender2&& completionSender, Receiver2&& r) : completionSender_(static_cast(completionSender)) , receiver_(static_cast(r)) , sourceOp_{} { sourceOp_.construct_with([&] { return unifex::connect( static_cast(sourceSender), receiver_t{this}); }); } ~type() { if (!started_) { unifex::deactivate_union_member(sourceOp_); } } void start() & noexcept { UNIFEX_ASSERT(!started_); started_ = true; unifex::start(sourceOp_.get()); } private: UNIFEX_NO_UNIQUE_ADDRESS remove_cvref_t completionSender_; UNIFEX_NO_UNIQUE_ADDRESS Receiver receiver_; bool started_ = false; // Result storage. union { // Storage for error-types that might be produced by SourceSender. UNIFEX_NO_UNIQUE_ADDRESS sender_error_types_t, error_result_union> error_; // Storage for value-types that might be produced by SourceSender. UNIFEX_NO_UNIQUE_ADDRESS sender_value_types_t< remove_cvref_t, manual_lifetime_union, decayed_tuple::template apply> value_; }; using source_operation_t = manual_lifetime>>; using completion_value_union_t = sender_value_types_t< remove_cvref_t, manual_lifetime_union, value_operation>; using completion_error_union_t = sender_error_types_t, error_operation_union>; // Operation storage. union { // Storage for the source operation state. source_operation_t sourceOp_; // Storage for the completion operation for the case where // the source operation completed with a value. completion_value_union_t completionValueOp_; // Storage for the completion operation for the case where the // source operation completed with an error. completion_error_union_t completionErrorOp_; // Storage for the completion operation for the case where the // source operation completed with 'done'. manual_lifetime completionDoneOp_; }; }; template struct _sender { class type; }; template using sender = typename _sender< remove_cvref_t, remove_cvref_t>::type; template class _sender::type { using sender = type; public: template < template class Variant, template class Tuple> using value_types = typename sender_traits:: template value_types::template apply>; // This can produce any of the error_types of SourceSender, or of // CompletionSender or an exception_ptr corresponding to an exception thrown // by the copy/move of the value result. // TODO: In theory we could eliminate exception_ptr in the case that the // connect() operation and move/copy of values template