aboutsummaryrefslogtreecommitdiff
path: root/pw_rpc/public/pw_rpc/internal/synchronous_call_impl.h
diff options
context:
space:
mode:
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.h200
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