diff options
Diffstat (limited to 'pw_rpc/public/pw_rpc/internal/synchronous_call_impl.h')
-rw-r--r-- | pw_rpc/public/pw_rpc/internal/synchronous_call_impl.h | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/pw_rpc/public/pw_rpc/internal/synchronous_call_impl.h b/pw_rpc/public/pw_rpc/internal/synchronous_call_impl.h new file mode 100644 index 000000000..7ca7b991e --- /dev/null +++ b/pw_rpc/public/pw_rpc/internal/synchronous_call_impl.h @@ -0,0 +1,200 @@ +// Copyright 2023 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// 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 <utility> + +#include "pw_rpc/internal/config.h" +#include "pw_rpc/internal/method_info.h" +#include "pw_rpc/synchronous_call_result.h" +#include "pw_sync/timed_thread_notification.h" + +#if PW_RPC_DYNAMIC_ALLOCATION +#include PW_RPC_MAKE_UNIQUE_PTR_INCLUDE +#endif // PW_RPC_DYNAMIC_ALLOCATION + +namespace pw::rpc::internal { + +template <typename Response> +struct SynchronousCallState { + auto OnCompletedCallback() { + return [this](const Response& response, Status status) { + result = SynchronousCallResult<Response>(status, response); + notify.release(); + }; + } + + auto OnRpcErrorCallback() { + return [this](Status status) { + result = SynchronousCallResult<Response>::RpcError(status); + notify.release(); + }; + } + + SynchronousCallResult<Response> result; + sync::TimedThreadNotification notify; +}; + +class RawSynchronousCallState { + public: + RawSynchronousCallState(Function<void(ConstByteSpan, Status)> on_completed) + : on_completed_(std::move(on_completed)) {} + + auto OnCompletedCallback() { + return [this](ConstByteSpan response, Status status) { + if (on_completed_) { + on_completed_(response, status); + } + notify.release(); + }; + } + + auto OnRpcErrorCallback() { + return [this](Status status) { + error = status; + notify.release(); + }; + } + + Status error; + sync::TimedThreadNotification notify; + + private: + Function<void(ConstByteSpan, Status)> on_completed_; +}; + +// Overloaded function to choose detween timeout and deadline APIs. +inline bool AcquireNotification(sync::TimedThreadNotification& notification, + chrono::SystemClock::duration timeout) { + return notification.try_acquire_for(timeout); +} + +inline bool AcquireNotification(sync::TimedThreadNotification& notification, + chrono::SystemClock::time_point timeout) { + return notification.try_acquire_until(timeout); +} + +template <auto kRpcMethod, + typename Response = typename MethodInfo<kRpcMethod>::Response, + typename DoCall, + typename... TimeoutArg> +SynchronousCallResult<Response> StructSynchronousCall( + DoCall&& do_call, TimeoutArg... timeout_arg) { + static_assert(MethodInfo<kRpcMethod>::kType == MethodType::kUnary, + "Only unary methods can be used with synchronous calls"); + + // If dynamic allocation is enabled, heap-allocate the call_state. +#if PW_RPC_DYNAMIC_ALLOCATION + auto call_state_ptr = PW_RPC_MAKE_UNIQUE_PTR(SynchronousCallState<Response>); + SynchronousCallState<Response>& call_state(*call_state_ptr); +#else + SynchronousCallState<Response> call_state; +#endif // PW_RPC_DYNAMIC_ALLOCATION + + auto call = std::forward<DoCall>(do_call)(call_state); + + // Wait for the notification based on the type of the timeout argument. + if constexpr (sizeof...(TimeoutArg) == 0) { + call_state.notify.acquire(); // Wait forever, since no timeout was given. + } else if (!AcquireNotification(call_state.notify, timeout_arg...)) { + return SynchronousCallResult<Response>::Timeout(); + } + + return std::move(call_state.result); +} + +// Template for a raw synchronous call. Used for SynchronousCall, +// SynchronousCallFor, and SynchronousCallUntil. The type of the timeout +// argument is used to determine the behavior. +template <auto kRpcMethod, typename DoCall, typename... TimeoutArg> +Status RawSynchronousCall(Function<void(ConstByteSpan, Status)>&& on_completed, + DoCall&& do_call, + TimeoutArg... timeout_arg) { + static_assert(MethodInfo<kRpcMethod>::kType == MethodType::kUnary, + "Only unary methods can be used with synchronous calls"); + + RawSynchronousCallState call_state{std::move(on_completed)}; + + auto call = std::forward<DoCall>(do_call)(call_state); + + // Wait for the notification based on the type of the timeout argument. + if constexpr (sizeof...(TimeoutArg) == 0) { + call_state.notify.acquire(); // Wait forever, since no timeout was given. + } else if (!AcquireNotification(call_state.notify, timeout_arg...)) { + return Status::DeadlineExceeded(); + } + + return call_state.error; +} + +// Choose which call state object to use (raw or struct). +template <auto kRpcMethod, + typename Response = + typename internal::MethodInfo<kRpcMethod>::Response> +using CallState = std::conditional_t< + std::is_same_v<typename MethodInfo<kRpcMethod>::Request, void>, + RawSynchronousCallState, + SynchronousCallState<Response>>; + +// Invokes the RPC method free function using a call_state. +template <auto kRpcMethod, typename Request> +constexpr auto CallFreeFunction(Client& client, + uint32_t channel_id, + const Request& request) { + return [&client, channel_id, &request](CallState<kRpcMethod>& call_state) { + return kRpcMethod(client, + channel_id, + request, + call_state.OnCompletedCallback(), + call_state.OnRpcErrorCallback()); + }; +} + +// Invokes the RPC method free function using a call_state and a custom +// response. +template < + auto kRpcMethod, + typename Response = typename internal::MethodInfo<kRpcMethod>::Response, + typename Request> +constexpr auto CallFreeFunctionWithCustomResponse(Client& client, + uint32_t channel_id, + const Request& request) { + return [&client, channel_id, &request]( + CallState<kRpcMethod, Response>& call_state) { + constexpr auto kMemberFunction = + MethodInfo<kRpcMethod>::template FunctionTemplate< + typename MethodInfo<kRpcMethod>::ServiceClass, + Response>(); + return (*kMemberFunction)(client, + channel_id, + request, + call_state.OnCompletedCallback(), + call_state.OnRpcErrorCallback()); + }; +} + +// Invokes the RPC function on the generated service client using a call_state. +template <auto kRpcMethod, typename GeneratedClient, typename Request> +constexpr auto CallGeneratedClient(const GeneratedClient& client, + const Request& request) { + return [&client, &request](CallState<kRpcMethod>& call_state) { + constexpr auto kMemberFunction = + MethodInfo<kRpcMethod>::template Function<GeneratedClient>(); + return (client.*kMemberFunction)(request, + call_state.OnCompletedCallback(), + call_state.OnRpcErrorCallback()); + }; +} + +} // namespace pw::rpc::internal |