nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
future.hpp
Go to the documentation of this file.
1// nros-cpp: Future<T> -- single-shot deferred result
2// Freestanding C++ -- no exceptions, no STL required
3
10#ifndef NROS_CPP_FUTURE_HPP
11#define NROS_CPP_FUTURE_HPP
12
13#include <cstdint>
14#include <cstddef>
15
16#include "nros/result.hpp"
17#include "nros/size_bound.hpp" // nros::rx_buffer_capacity<T> — the receive-buffer size
18
19// FFI declarations
20extern "C" {
21typedef int nros_cpp_ret_t;
22nros_cpp_ret_t nros_cpp_spin_once(void* handle, int32_t timeout_ms);
25uint64_t nros_cpp_time_ns(void);
26}
27
28namespace nros {
29
49template <typename T, size_t Cap = ::nros::rx_buffer_capacity<T>::value> class Future {
50 public:
52 bool is_ready() {
53 if (slot_ < 0 || !take_fn_) return false;
54 if (ready_) return true;
55 uint8_t buf[Cap];
56 size_t len = 0;
57 nros_cpp_ret_t ret = take_fn_(client_storage_, buf, sizeof(buf), &len);
58 if (ret == 0 && len > 0) {
59 ready_ = true;
60 cached_len_ = len < sizeof(cached_buf_) ? len : sizeof(cached_buf_);
61 for (size_t i = 0; i < cached_len_; ++i)
62 cached_buf_[i] = buf[i];
63 return true;
64 }
65 return false;
66 }
67
72 Result try_take(T& out) {
73 if (slot_ < 0) return Result(ErrorCode::Error);
74 if (!ready_ && !is_ready()) return Result(ErrorCode::Error);
75 slot_ = -1; // consume
76 if (T::ffi_deserialize(cached_buf_, cached_len_, &out) != 0) {
78 }
79 return Result::success();
80 }
81
93 Result wait(void* executor_handle, uint32_t timeout_ms, T& out, uint32_t poll_ms = 10) {
94 if (slot_ < 0) return Result(ErrorCode::Error);
95 if (poll_ms == 0) poll_ms = 1;
96 // Phase 89.2: budget by wall-clock. Accumulating `step` per iteration
97 // breaks when the underlying `zpico_spin_once` returns early on a
98 // signaled condvar (keep-alives, discovery gossip) — the 500-step
99 // default-timeout loop can then collapse into milliseconds and
100 // return ErrorCode::Timeout before the reply has a chance to land.
101 const uint64_t start_ns = nros_cpp_time_ns();
102 const uint64_t budget_ns = static_cast<uint64_t>(timeout_ms) * 1000000ULL;
103 while (true) {
104 nros_cpp_ret_t ret = nros_cpp_spin_once(executor_handle, static_cast<int32_t>(poll_ms));
105 // Transient conditions: keep polling. Anything else propagates.
106 // - Ok (0): nothing to dispatch this round.
107 // - Timeout (-2): spin_once returned after its timeout — normal.
108 // - TryAgain (-6): transport hint to retry.
109 if (ret != 0 && ret != static_cast<nros_cpp_ret_t>(ErrorCode::Timeout) &&
110 ret != static_cast<nros_cpp_ret_t>(ErrorCode::TryAgain)) {
111 return Result(ret);
112 }
113 if (is_ready()) return try_take(out);
114 const uint64_t now_ns = nros_cpp_time_ns();
115 if (now_ns - start_ns >= budget_ns) break;
116 }
118 }
119
121 void cancel() { slot_ = -1; }
122
124 bool is_consumed() const { return slot_ < 0; }
125
126 // Move semantics (non-copyable, single-shot)
127 Future(Future&& other) noexcept
128 : client_storage_(other.client_storage_), take_fn_(other.take_fn_), slot_(other.slot_),
129 ready_(other.ready_), cached_len_(other.cached_len_) {
130 for (size_t i = 0; i < cached_len_; ++i)
131 cached_buf_[i] = other.cached_buf_[i];
132 other.slot_ = -1;
133 other.ready_ = false;
134 }
135
136 Future& operator=(Future&& other) noexcept {
137 if (this != &other) {
138 client_storage_ = other.client_storage_;
139 take_fn_ = other.take_fn_;
140 slot_ = other.slot_;
141 ready_ = other.ready_;
142 cached_len_ = other.cached_len_;
143 for (size_t i = 0; i < cached_len_; ++i)
144 cached_buf_[i] = other.cached_buf_[i];
145 other.slot_ = -1;
146 other.ready_ = false;
147 }
148 return *this;
149 }
150
152
155 : client_storage_(nullptr), take_fn_(nullptr), slot_(-1), ready_(false), cached_len_(0) {}
156
157 private:
158 Future(const Future&) = delete;
159 Future& operator=(const Future&) = delete;
160
161 template <typename S> friend class Client;
162 template <typename A> friend class ActionClient;
163
164 using TryRecvFn = nros_cpp_ret_t (*)(void*, uint8_t*, size_t, size_t*);
165
166 Future(void* storage, TryRecvFn fn, int slot)
167 : client_storage_(storage), take_fn_(fn), slot_(slot), ready_(false), cached_len_(0) {}
168
169 void* client_storage_;
170 TryRecvFn take_fn_;
171 int slot_;
172 bool ready_;
173 size_t cached_len_;
174 uint8_t cached_buf_[Cap];
175};
176
177} // namespace nros
178#endif // NROS_CPP_FUTURE_HPP
Definition action_client.hpp:73
Definition client.hpp:55
Definition future.hpp:49
Result wait(void *executor_handle, uint32_t timeout_ms, T &out, uint32_t poll_ms=10)
Definition future.hpp:93
void cancel()
Cancel the pending operation (idempotent).
Definition future.hpp:121
Future & operator=(Future &&other) noexcept
Definition future.hpp:136
~Future()
Definition future.hpp:151
Result try_take(T &out)
Definition future.hpp:72
Future(Future &&other) noexcept
Definition future.hpp:127
Future()
Default constructor – creates an empty/consumed future.
Definition future.hpp:154
bool is_consumed() const
Check if the future has been consumed or cancelled.
Definition future.hpp:124
bool is_ready()
Check if the result has arrived (non-blocking).
Definition future.hpp:52
Definition result.hpp:90
static constexpr Result success()
Named constructors.
Definition result.hpp:112
int nros_cpp_ret_t
Definition future.hpp:21
nros_cpp_ret_t nros_cpp_spin_once(void *handle, int32_t timeout_ms)
uint64_t nros_cpp_time_ns(void)
Definition nros.hpp:55
@ Error
Generic failure not covered by a more specific code.
@ Timeout
Operation deadline elapsed before completion.
@ TryAgain
Transient — no data ready yet (non-blocking take). Retry later.
nros::Result, nros::ErrorCode, and the NROS_TRY macro.