| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef LIB_ASYNC_PATTERNS_CPP_PENDING_CALL_H_ |
| #define LIB_ASYNC_PATTERNS_CPP_PENDING_CALL_H_ |
| |
| #include <lib/async_patterns/cpp/internal/tag.h> |
| #include <lib/fpromise/bridge.h> |
| #include <zircon/assert.h> |
| |
| #include <utility> |
| |
| namespace async_patterns { |
| |
| template <typename F> |
| class Callback; |
| |
| // This type is usually returned from a |DispatcherBound::AsyncCall| or calling |
| // |Callback<ReturnType(Args...)>|. |
| // |
| // Let |Call| be a callable that takes zero arguments and returns |ReturnType|. |
| // |PendingCall| represents a call that is yet to be run, and offers a variety |
| // of ways to monitor its return value: |
| // |
| // - The caller may discard the |PendingCall|, at which point the call will be |
| // submitted for execution but its return value will be ignored. |
| // |
| // - The caller may call `promise()` to get a `fpromise::promise<ReturnType>` |
| // that will resolve if the call runs to completion, or be abandoned if the |
| // call is dropped. |
| // |
| // - The caller may call `Then` to specify an `async_patterns::Callback<void(R)>` |
| // that will be called when the call runs to completion. |
| // |
| template <typename ReturnType, typename Call, typename Submit> |
| class PendingCall { |
| public: |
| // Make the call if not already, and ignore the result. |
| // |
| // This leads to "fire-and-forget" behavior: |
| // |
| // async_patterns::DispatcherBound<MyType> object; |
| // |
| // // This returns a |PendingCall|. If we do nothing with the return |
| // // value, that means making the call and we don't care about its result. |
| // object.AsyncCall(&MyType::SomeMethod); |
| // |
| ~PendingCall() { |
| if (submit_.has_value()) { |
| submit([call = std::move(call_)]() mutable { (void)call(); }); |
| } |
| } |
| |
| // Make the call and return a promise representing the return value of the call. |
| // |
| // The promise will resolve if the call runs to completion. |
| // |
| // The promise will be abandoned if the call is dropped, such as if the target object |
| // that is supposed to respond to this async call is already destroyed. |
| // |
| // Example: |
| // |
| // async_patterns::Callback<std::string(int)> callback = ...; |
| // fpromise::promise<int> promise = callback(42).promise(); |
| // |
| // // Now you can do something with the promise.. |
| // executor.schedule_task( |
| // promise.and_then([] (int& value) { ... })); |
| // |
| fpromise::promise<ReturnType> promise() && { |
| fpromise::bridge<ReturnType> bridge; |
| submit([call = std::move(call_), completer = bridge.completer.bind()]() mutable { |
| using CallReturnType = decltype(call()); |
| static_assert(std::is_convertible_v<CallReturnType, ReturnType>); |
| if constexpr (std::is_void_v<ReturnType>) { |
| call(); |
| completer(); |
| } else { |
| completer(call()); |
| } |
| }); |
| return bridge.consumer.promise(); |
| } |
| |
| // Arranges |on_result| to be called with the result of the async call. |
| // |on_result| is an |async_patterns::Callback<void(R)>|. |R| could be |
| // identical to |ReturnType| or be some other compatible type such as |const |
| // ReturnType&|. Typically, the owner will declare a |Receiver| to mint these |
| // callbacks. |
| // |
| // This allows two thread-unsafe objects living on different dispatchers to |
| // exchange messages in a ping-pong fashion. Example: |
| // |
| // class Owner { |
| // public: |
| // Owner(async_dispatcher_t* owner_dispatcher) |
| // : receiver_{this, owner_dispatcher}, |
| // background_loop_(&kAsyncLoopConfigNoAttachToCurrentThread), |
| // background_{background_loop_.dispatcher(), std::in_place} {} |
| // |
| // void StartDoingStuff() { |
| // // Make a call on |Background|, and then receive the result |
| // // at |DoneDoingStuff|. |
| // background_.AsyncCall(&Background::DoStuff, 42) |
| // .Then(receiver_.Once(&Owner::DoneDoingStuff)); |
| // } |
| // |
| // void DoneDoingStuff(std::string result) { |
| // // Check the result from |Background::DoStuff|. |
| // } |
| // |
| // private: |
| // async_patterns::Receiver<Owner> receiver_; |
| // async::Loop background_loop_; |
| // async_patterns::DispatcherBound<Background> background_; |
| // }; |
| // |
| // See more in |async_patterns::DispatcherBound|. |
| template <typename R> |
| void Then(async_patterns::Callback<void(R)> on_result) && { |
| constexpr bool kReceiverMatchesReturnValue = |
| std::is_invocable_v<decltype(on_result), ReturnType>; |
| static_assert(kReceiverMatchesReturnValue, |
| "The |async_patterns::Callback<void(R)>| must accept the return value " |
| "of the async call."); |
| if constexpr (kReceiverMatchesReturnValue) { |
| submit([call = std::move(call_), on_result = std::move(on_result)]() mutable { |
| if constexpr (std::is_void_v<ReturnType>) { |
| call(); |
| on_result(); |
| } else { |
| on_result(call()); |
| } |
| }); |
| } |
| } |
| |
| /// |Call| should be a callable that takes zero arguments and returns |ReturnType|. |
| /// |
| /// |Submit| should be a callable that takes a |Call| and submits it for |
| /// asynchronous execution. In addition, it should have an empty state reachable |
| /// by calling |reset| and support checking for emptiness using |has_value|. |
| PendingCall(Call call, Submit submit, internal::Tag<ReturnType>) |
| : call_(std::move(call)), submit_(std::move(submit)) {} |
| |
| PendingCall(const PendingCall&) = delete; |
| PendingCall& operator=(const PendingCall&) = delete; |
| PendingCall(PendingCall&&) noexcept = delete; |
| PendingCall& operator=(PendingCall&&) noexcept = delete; |
| |
| protected: |
| template <typename Continuation> |
| void CallWithContinuation(Continuation continuation) { |
| submit([call = std::move(call_), c = std::move(continuation)]() mutable { c(call()); }); |
| } |
| |
| private: |
| template <typename Task> |
| void submit(Task&& task) { |
| ZX_DEBUG_ASSERT(submit_.has_value()); |
| submit_(std::forward<Task>(task)); |
| submit_.reset(); |
| } |
| |
| Call call_; |
| Submit submit_; |
| }; |
| |
| } // namespace async_patterns |
| |
| #endif // LIB_ASYNC_PATTERNS_CPP_PENDING_CALL_H_ |