blob: 4f0f08756b8f3b43d485b423f7bd8247d3280683 [file] [log] [blame] [edit]
// 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_