blob: a6a24880977808bf89a5d00aac72847b92b3b7d6 [file] [log] [blame]
// 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_;
};
template <typename ReturnType, typename Call, typename Submit>
PendingCall(Call call, Submit submit, internal::Tag<ReturnType>)
-> PendingCall<ReturnType, Call, Submit>;
} // namespace async_patterns
#endif // LIB_ASYNC_PATTERNS_CPP_PENDING_CALL_H_