nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
stream.hpp
Go to the documentation of this file.
1// nros-cpp: Stream<T> -- multi-shot message receiver
2// Freestanding C++ -- no exceptions, no STL required
3
10#ifndef NROS_RCLCPP_STREAM_HPP
11#define NROS_RCLCPP_STREAM_HPP
12
13#include <cstdint>
14#include <cstddef>
15
16#include "rclcpp/result.hpp"
17#include "rclcpp/size_bound.hpp" // rclcpp::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);
26uint64_t nros_cpp_time_ns(void);
27}
28
29// phase-428 (RFC-0089): the entity types that reach into this one's private
30// constructor are DEFINED in the upstream namespace now, so the friend
31// declarations below name them there. A qualified friend cannot introduce a
32// name, hence these declarations.
33namespace rclcpp {
34template <typename S> class Client;
35} // namespace rclcpp
36
37// phase-456 W2b — the subscription that BINDS a stream is the poll one: a
38// stream reads through `nros_cpp_subscription_take_serialized` on a
39// caller-owned `RmwSubscriber`, which a dispatch registration does not have.
40namespace rclcpp {
41template <typename M> class PollSubscription;
42} // namespace rclcpp
43
44namespace rclcpp_action {
45template <typename A> class Client;
46} // namespace rclcpp_action
47
48namespace rclcpp {
49
62template <typename T> class Stream {
63 public:
73
76 template <size_t Cap> Result try_next_sized(T& out) {
77 if (!take_fn_) return Result(ErrorCode::NotInitialized);
79 size_t len = 0;
80 nros_cpp_ret_t ret = take_fn_(storage_, buf, sizeof(buf), &len);
81 if (ret != 0) return Result(ret);
82 if (len == 0) return Result(ErrorCode::TryAgain);
83 if (T::ffi_deserialize(buf, len, &out) != 0) return Result(ErrorCode::Error);
84 return Result::success();
85 }
86
97 Result wait_next(void* executor_handle, uint32_t timeout_ms, T& out, uint32_t poll_ms = 10) {
99 out, poll_ms);
100 }
101
104 template <size_t Cap>
105 Result wait_next_sized(void* executor_handle, uint32_t timeout_ms, T& out,
106 uint32_t poll_ms = 10) {
107 if (!take_fn_) return Result(ErrorCode::NotInitialized);
108 if (poll_ms == 0) poll_ms = 1;
109 // Phase 118.C: budget by wall-clock. Accumulating `step` per
110 // iteration breaks when `zpico_spin_once` returns early on a
111 // signaled condvar (keep-alives, discovery gossip) — the
112 // iteration loop collapses into milliseconds and returns
113 // Timeout before the message has a chance to land. Mirrors the
114 // Phase 89.2 fix on Future::wait().
116 const uint64_t budget_ns = static_cast<uint64_t>(timeout_ms) * 1000000ULL;
117 while (true) {
118 nros_cpp_ret_t ret = nros_cpp_spin_once(executor_handle, static_cast<int32_t>(poll_ms));
119 // Transient conditions: keep polling. Anything else propagates.
120 if (ret != 0 && ret != static_cast<nros_cpp_ret_t>(ErrorCode::Timeout) &&
121 ret != static_cast<nros_cpp_ret_t>(ErrorCode::TryAgain)) {
122 return Result(ret);
123 }
125 if (rn.ok()) return Result::success();
126 // TryAgain / NotInitialized / Error from try_next: keep polling
127 // unless we've hit a hard error that's not "no data yet".
128 if (rn.code() != ErrorCode::TryAgain && rn.code() != ErrorCode::NotInitialized) {
129 return rn;
130 }
132 if (now_ns - start_ns >= budget_ns) break;
133 }
135 }
136
138 bool is_valid() const { return take_fn_ != nullptr; }
139
140 // Move semantics (non-copyable)
141 Stream(Stream&& other) noexcept : storage_(other.storage_), take_fn_(other.take_fn_) {
142 other.storage_ = nullptr;
143 other.take_fn_ = nullptr;
144 }
145
147 if (this != &other) {
148 storage_ = other.storage_;
149 take_fn_ = other.take_fn_;
150 other.storage_ = nullptr;
151 other.take_fn_ = nullptr;
152 }
153 return *this;
154 }
155
157 Stream() : storage_(nullptr), take_fn_(nullptr) {}
158
159 private:
160 Stream(const Stream&) = delete;
161 Stream& operator=(const Stream&) = delete;
162
163 template <typename M> friend class ::rclcpp::PollSubscription;
164 template <typename A> friend class ::rclcpp_action::Client;
165
166 using TakeFn = nros_cpp_ret_t (*)(void*, uint8_t*, size_t, size_t*);
167
168 Stream(void* storage, TakeFn fn) : storage_(storage), take_fn_(fn) {}
169
170 void bind(void* storage, TakeFn fn) {
171 storage_ = storage;
172 take_fn_ = fn;
173 }
174
175 void* storage_;
176 TakeFn take_fn_;
177};
178
179} // namespace rclcpp
180#endif // NROS_RCLCPP_STREAM_HPP
Definition client.hpp:129
Definition result.hpp:216
static constexpr ResultOf success()
Named constructors.
Definition result.hpp:238
Definition stream.hpp:62
Stream()
Default constructor – creates an unbound stream.
Definition stream.hpp:157
Stream(Stream &&other) noexcept
Definition stream.hpp:141
Result wait_next_sized(void *executor_handle, uint32_t timeout_ms, T &out, uint32_t poll_ms=10)
Definition stream.hpp:105
bool is_valid() const
Check if the stream is connected to a valid source.
Definition stream.hpp:138
Stream & operator=(Stream &&other) noexcept
Definition stream.hpp:146
friend class ::rclcpp_action::Client
Definition stream.hpp:164
Result try_next(T &out)
Definition stream.hpp:72
Result try_next_sized(T &out)
Definition stream.hpp:76
Result wait_next(void *executor_handle, uint32_t timeout_ms, T &out, uint32_t poll_ms=10)
Definition stream.hpp:97
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 node.hpp:199
Definition rclcpp.hpp:75
ResultOf< void > Result
Definition result.hpp:246
@ 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.
rclcpp::Result, rclcpp::ErrorCode, and the RCLCPP_TRY macro.
int nros_cpp_ret_t
Definition stream.hpp:21
nros_cpp_ret_t nros_cpp_spin_once(void *handle, int32_t timeout_ms)
uint64_t nros_cpp_time_ns(void)