| // 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. |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <variant> |
| |
| #include "public/pw_async_bench/base.h" |
| #include "pw_async_basic/dispatcher.h" |
| #include "pw_async_bench/base.h" |
| #include "pw_async_bench/bivariant.h" |
| #include "pw_async_bench/poll.h" |
| #include "pw_result/result.h" |
| #include "pw_thread/thread.h" |
| #include "pw_thread_stl/options.h" |
| |
| using pw::async_bench::EchoRequest; |
| using pw::async_bench::EchoResponse; |
| |
| class RemoteEcho { |
| public: |
| RemoteEcho() {} |
| |
| class EchoFuture { |
| public: |
| pw::async::Poll<pw::Result<EchoResponse>> Poll(pw::async::Waker& waker) { |
| // Do some shenanigans so that we're actually exercising the |
| // `Poll`/`Waker` mechanism. |
| if (is_first_time_) { |
| is_first_time_ = false; |
| waker.Wake(); |
| return pw::async::Pending(); |
| } |
| return pw::Result<EchoResponse>(EchoResponse{std::move(value_)}); |
| } |
| |
| private: |
| std::string value_; |
| bool is_first_time_ = true; |
| }; |
| EchoFuture Echo([[maybe_unused]] EchoRequest request) { return EchoFuture(); } |
| }; |
| |
| template <typename Impl> |
| void PostEcho(pw::async::Dispatcher& dispatcher, |
| Impl& impl, |
| EchoRequest request, |
| std::optional<pw::Result<EchoResponse>>& result_out) { |
| // This is sort of like a `Task` result oneshot channel. |
| // In a more "real" version, we'd use such a channel instead of a manual |
| // out pointer (which presents lifetime scenarios if the caller stack may |
| // disappear). |
| class TaskData { |
| public: |
| TaskData(std::optional<pw::Result<EchoResponse>>* result_out, |
| typename Impl::EchoFuture&& echo_future) |
| : result_out_(result_out), echo_future_(std::move(echo_future)) {} |
| |
| std::optional<pw::Result<EchoResponse>>* result_out_; |
| typename Impl::EchoFuture echo_future_; |
| }; |
| auto task_data = |
| std::make_unique<TaskData>(&result_out, impl.Echo(std::move(request))); |
| |
| auto task = std::make_unique<pw::async::Task>( |
| [task_data = std::move(task_data)](pw::async::Context& context, |
| pw::Status status) { |
| // This status value isn't super meaningful in a poll-based world-- the |
| // task itself is alerted to the cancellation by seeing that it has been |
| // dropped. |
| if (status.IsCancelled()) { |
| return; |
| } |
| |
| pw::async::Waker waker(*context.dispatcher, *context.task); |
| pw::async::Poll<pw::Result<EchoResponse>> result = |
| task_data->echo_future_.Poll(waker); |
| if (result.IsReady()) { |
| *task_data->result_out_ = std::optional(std::move(result.value())); |
| } |
| }); |
| |
| dispatcher.Post(*task); |
| |
| // `pw_async` does not currently provide hooks for knowing |
| // when a task has been `Post`ed or when it has been cancelled |
| // (no, the `Cancelled` status does not communicate this clearly today), |
| // so we need to leak top-level `Task` (or allocate it statically). |
| // |
| // If we decide to adopt the poll-based model, this should be changed. |
| task.release(); |
| } |
| |
| // --- USER CODE BEGIN --- // |
| |
| class ProxyEchoImpl { |
| public: |
| ProxyEchoImpl(RemoteEcho& remote) : remote_(&remote) {} |
| |
| // Note: this wrapper future is used to demonstrate how futures can |
| // be composed, but it is unnecessary: `Echo` could simply return the |
| // future from `RemoteEcho` directly, avoiding any extra overhead or |
| // boilerplate. |
| // |
| // Similarly, the future need not take in a `RemoteEcho` or deal with the |
| // `Echo` call itself-- that could be done in the implementation of `Echo` |
| // for minimal boilerplate and maximal efficiency. However, this is intended |
| // as a demonstration of what a compositional `Poll`-able might look like. |
| class EchoFuture { |
| public: |
| EchoFuture(EchoRequest request, RemoteEcho& remote) |
| : state_(BeforeRemoteCall{std::move(request), &remote}) {} |
| pw::async::Poll<pw::Result<EchoResponse>> Poll(pw::async::Waker& waker) { |
| if (state_.is_a()) { |
| auto& before_call = state_.value_a(); |
| state_ = WaitingOnRemote{ |
| before_call.remote->Echo(std::move(before_call.request))}; |
| } |
| auto& waiting_on_remote = state_.value_b(); |
| return waiting_on_remote.remote_future.Poll(waker); |
| } |
| |
| private: |
| struct BeforeRemoteCall { |
| EchoRequest request; |
| RemoteEcho* remote; |
| }; |
| struct WaitingOnRemote { |
| RemoteEcho::EchoFuture remote_future; |
| }; |
| pw::bivariant<BeforeRemoteCall, WaitingOnRemote> state_; |
| }; |
| |
| EchoFuture Echo(EchoRequest request) { |
| return EchoFuture(std::move(request), *remote_); |
| } |
| |
| private: |
| RemoteEcho* remote_; |
| }; |
| |
| int main() { |
| pw::async::BasicDispatcher basic_dispatcher; |
| pw::thread::Thread work_thread(pw::thread::stl::Options(), basic_dispatcher); |
| |
| const char* ECHO_VALUE = "some value"; |
| EchoRequest request{ECHO_VALUE}; |
| std::optional<pw::Result<EchoResponse>> result_storage(std::nullopt); |
| |
| RemoteEcho remote; |
| ProxyEchoImpl impl(remote); |
| PostEcho(basic_dispatcher, impl, std::move(request), result_storage); |
| basic_dispatcher.RunUntilIdle(); |
| |
| PW_ASSERT(result_storage.has_value()); |
| PW_ASSERT(result_storage->ok()); |
| PW_ASSERT(result_storage->value().value == ECHO_VALUE); |
| return 0; |
| } |