nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
rclcpp.hpp
Go to the documentation of this file.
1// nros-cpp: Umbrella header
2// Include this single header to get the full nros C++ API.
3//
4// Freestanding C++ compatible — no STL, no exceptions, no RTTI required.
5
12#ifndef NROS_RCLCPP_RCLCPP_HPP
13#define NROS_RCLCPP_RCLCPP_HPP
14
15// Phase 118.D — pull cbindgen-generated FFI before any wrapper hpp so
16// `rclcpp/qos.hpp`'s `#ifndef NROS_CPP_FFI_H` guard skips its local
17// fallback definitions in favor of the canonical types.
18#include "rclcpp/detail/nros_cpp_ffi.h"
19
20#include "rclcpp/detail/traits.hpp"
21#include "rclcpp/logging.hpp"
22#include "rclcpp/result.hpp"
23// Issue 0789 — the clock / time / duration surface. `node.now()` and
24// `node.get_clock()->now()` are what a ported rclcpp publisher calls to
25// stamp a header, so the umbrella carries all three. Phase 379 W5 moved
26// `duration.hpp` ABOVE `qos.hpp`: the QoS deadline / lifespan / lease
27// accessors take and return `rclcpp::Duration`. `qos.hpp` also includes it
28// directly, so this order is legibility rather than load-bearing.
29#include "rclcpp/duration.hpp"
30#include "rclcpp/time.hpp"
31#include "rclcpp/clock.hpp"
32#include "rclcpp/qos.hpp"
33#include "rclcpp/options.hpp"
34#include "rclcpp/future.hpp"
35#include "rclcpp/stream.hpp"
36// RFC-0088 D5 — rclcpp::SerializationFormat / format_of<M> / linked_format().
38// Phase 84.G8: node.hpp no longer pulls in the heavy entity headers —
39// each entity header carries its own out-of-line `Node::create_X<>()`
40// template definition. The umbrella pulls in every entity explicitly so
41// `#include <rclcpp/rclcpp.hpp>` still yields the full API.
42#include "rclcpp/node.hpp"
43#include "rclcpp/publisher.hpp"
45#include "rclcpp/service.hpp"
46#include "rclcpp/client.hpp"
49#include "rclcpp/polling_action_server.hpp"
50#include "rclcpp/polling_action_client.hpp"
51#include "rclcpp/polling_client.hpp"
52#include "rclcpp/polling_service.hpp"
53#include "rclcpp/polling_subscription.hpp"
54#include "rclcpp/parameter.hpp"
55// phase-426 W4 — the ONE parameter facade. Forwards a node's
56// declare/get/set/has onto the executor's store across the FFI, so a parameter
57// means the same thing wherever it is declared. There is now one node type to
58// wear it (phase-427 W4), which is the other half of that guarantee.
59// Freestanding: `<string>`/`<vector>` behind `NROS_CPP_STD`.
60#include "rclcpp/node_parameters.hpp"
61#include "rclcpp/tick_ctx.hpp"
62#include "rclcpp_lifecycle/lifecycle_node.hpp"
63#include "rclcpp/rosout.hpp" // issue 1589 — `/rosout` from C++
64// phase-427 W4 — `nros/component_node.hpp` IS GONE. `nros::ComponentNode` was a
65// type that WRAPPED a node (RFC-0044 Q1's "wrap, not derive"), so a component
66// was not a `Node` and every verb had to be forwarded. Its members are on
67// `rclcpp::Node` now, its pool is the opt-in `rclcpp::NodeWithTimers<N>`, and its
68// macros are in `component.hpp` below. The rclcpp-shaped, value-returning
69// parameter facade phase-417 W2.b pulled in here for is `Node`'s own.
70// phase-417 stage 6 step A — `rclcpp::create_subscription_raw`, the
71// arena-registration entry point `rclcpp::Node::create_subscription`
72// below uses so it has no dispatch of its own.
73#include "rclcpp/detail/component.hpp"
74
75namespace rclcpp {
76
88inline void* global_handle();
89
98 if (!::rclcpp::Node::global_initialized()) {
100 }
101 return Result(nros_cpp_spin_once(::rclcpp::Node::global_storage(), timeout_ms));
102}
103
113inline void arm_monitors() {
115}
116
137inline Result pre_shutdown(ShutdownCallback callback, void* context = nullptr,
138 PreShutdownCallbackHandle* out = nullptr) {
139 void* executor = global_handle();
140 if (executor == nullptr) {
142 }
145 if (out != nullptr) *out = PreShutdownCallbackHandle(raw);
146 return Result(ret);
147}
148
156inline Result on_shutdown(ShutdownCallback callback, void* context = nullptr,
157 OnShutdownCallbackHandle* out = nullptr) {
158 void* executor = global_handle();
159 if (executor == nullptr) {
161 }
164 if (out != nullptr) *out = OnShutdownCallbackHandle(raw);
165 return Result(ret);
166}
167
171 void* executor = global_handle();
172 if (executor == nullptr) return false;
173 return nros_cpp_remove_pre_shutdown_callback(executor, handle.value()) == 0;
174}
175
179 void* executor = global_handle();
180 if (executor == nullptr) return false;
181 return nros_cpp_remove_on_shutdown_callback(executor, handle.value()) == 0;
182}
183
192inline Result spin_in() {
193 if (!::rclcpp::Node::global_initialized()) {
195 }
197 while (ok()) {
198 last = Result(nros_cpp_spin_once(::rclcpp::Node::global_storage(), 10));
199 if (!last.ok()) return last;
200 }
201 return last;
202}
203
213 if (!::rclcpp::Node::global_initialized()) {
215 }
216 // Issue 0329 — forward to the single budgeted-spin CFFI entry point. This
217 // loop previously budgeted by ITERATION count (`elapsed += timeout`), which
218 // collapsed to milliseconds when `spin_once` returned early on a signaled
219 // wake — the exact bug `Executor::spin` fixed in Phase 118.C. The correct
220 // wall-clock budget now lives once, Rust-side, in `nros_cpp_spin_for`.
221 return Result(nros_cpp_spin_for(::rclcpp::Node::global_storage(), duration_ms, poll_ms));
222}
223
224} // namespace rclcpp
225
226// ============================================================================
227// rclcpp:: — the process-level surface (RFC-0089 stage 6, step A)
228// ============================================================================
229//
230// `rclcpp::init` / `shutdown` / `ok` / `Node` / `spin` / `spin_some` / `Rate` /
231// `spin_until_future_complete` used to live in `nros/rclcpp_compat.hpp`, a
232// separate header a ported file had to be force-included with. RFC-0089
233// §"Naming: replace, with alias as the migration step" makes the ROS 2 spelling
234// a FIRST-CLASS name declared by the API headers themselves; §"End state: no
235// compat layer survives" is what follows from it — with one spelling per
236// concept there is nothing left for a shim to bridge, so it dissolves by
237// construction rather than having to be argued away. Step B — retiring the
238// in-tree `nros::` spelling — landed in phase-483 W1: `rclcpp::` is now the only
239// namespace.
240//
241// They land HERE, in the umbrella, and not in `node.hpp`, because that is where
242// their dependencies are. `rclcpp::Node` reaches `rclcpp::create_subscription_raw`
243// (`component.hpp`), `rclcpp::Timer`, `rclcpp::Service` and `rclcpp::Client`,
244// every one of which includes `node.hpp` itself; `Rate::sleep` and
245// `spin_until_future_complete` drive the `rclcpp::spin` / `rclcpp::spin_once`
246// free functions defined a few lines above. This IS the header they were
247// shimming: the process-level verbs — `rclcpp::init`, `rclcpp::spin`,
248// `rclcpp::spin_once`, `rclcpp::on_shutdown` — are all reached through
249// `<rclcpp/rclcpp.hpp>`.
250//
251// FREESTANDING. Unlike the shim it replaces, this is not hosted-STL by
252// construction. `init` / `shutdown` / `ok` and the `--ros-args` predicate need
253// nothing but `<cstdlib>`, so they reach a `no_std` C++ build. `Node`, the spin
254// forwarders and `Rate` are gated on the standard-library pieces their
255// signatures are spelled in (`__has_include`, never `__STDC_HOSTED__` — issue
256// 0112, rationale in `publisher.hpp`), so on a minimal libcpp they are absent
257// rather than a parse error.
258
259#include <cstdlib> // std::abort -- the runtime half of RFC-0089 W3.b
260#include <type_traits> // the SFINAE guards on create_service / create_client
261
262namespace rclcpp {
263
264// --- Process-level lifecycle -------------------------------------------------
265//
266// `rclcpp::init()` / `rclcpp::shutdown()` / `rclcpp::ok()` are the
267// process-level handshake (nros tracks the shutdown flag).
268//
269// The TWO-ARGUMENT form is REFUSE-LOUD (RFC-0089 W3.b). It used to forward to
270// the one-argument `init()` and discard `argv`, so `--ros-args -r
271// chatter:=/other` — the single most common thing a ported `main` passes —
272// silently became a wrong-topic bug at runtime. Honouring it is remap
273// resolution, RFC-0020 violation class 4, and belongs beside
274// `rclcpp::resolve_name` rather than here; until it lands, the honest answer is a
275// loud abort carrying the migration.
276
277namespace detail {
284constexpr bool is_ros_args_flag(char const* s, int i = 0) {
285 return s == nullptr ? false
286 : "--ros-args"[i] == '\0' ? s[i] == '\0'
287 : s[i] != "--ros-args"[i] ? false
288 : is_ros_args_flag(s, i + 1);
289}
290
291constexpr bool argv_has_ros_args(int argc, char const* const* argv, int i = 0) {
292 return i >= argc ? false
293 : argv == nullptr ? false
295 : argv_has_ros_args(argc, argv, i + 1);
296}
297
337[[noreturn]] inline void abort_failed_create(const char* verb, const char* name, int32_t code) {
338 // issue 1303 — two records through `nros_log`, each short enough for its
339 // format buffer: the call (the name bounded here), then the fixed text,
340 // whose length `say_refused` checks at compile time. As one `NROS_ERROR`
341 // record it was over 1180 bytes, which a real sink cannot carry: the body
342 // is dropped and only an ellipsis is printed, so the process aborted
343 // without saying why.
344 RCLC_LOG_ERROR(log_handle(nullptr), "rclcpp::Node::%s(\"%.64s\") failed: rclcpp::ErrorCode %d",
345 verb, name != nullptr ? name : "", static_cast<int>(code));
347 ::std::abort();
348}
349
352inline void require_created(::rclcpp::Result r, const char* verb, const char* name) {
353 if (!r.ok()) {
355 }
356}
357} // namespace detail
358
384inline void init(int argc, char const* const* argv) {
386 ::rclcpp::detail::apply_ros_args(nullptr, argc, argv) != 0) {
387 ::std::abort();
388 }
390 // An init that failed leaves no executor to install into; nothing here is
391 // dropped that the failed init did not already drop with the session.
392 void* const executor = ::rclcpp::global_handle();
393 if (detail::argv_has_ros_args(argc, argv) && executor != nullptr &&
394 ::rclcpp::detail::apply_ros_args(executor, argc, argv) != 0) {
395 ::std::abort();
396 }
397}
398
400inline void init() {
402}
417inline bool shutdown() {
418 return ::rclcpp::shutdown_in().ok();
419}
420
435using ::rclcpp::spin_once;
436
448 SUCCESS,
450 TIMEOUT,
453};
454
455} // namespace rclcpp
456
457// -- the ported publisher line, UNGATED — phase-456 W5 ------------------------
458//
459// `auto pub = node->create_publisher<M>("chatter", 10);` compiles on every
460// target now, including `-ffreestanding -nostdinc++` against Zephyr's and
461// ThreadX's minimal libcpp. It returns `Publisher<M>::SharedPtr`, which is
462// `rclcpp::Owned<Publisher<M>>` — the publisher by value, `operator->` for
463// `pub->publish(m)`, no allocator, no control block, no `<memory>`.
464//
465// WHAT THE NODE STOPPED DOING, stated because it is a behaviour change.
466// The hosted body used to `make_shared` the publisher and push a second
467// reference into `hosted().owned_entities`, so the node co-owned it and a
468// ported body could drop the returned pointer mid-expression
469// (`node->create_publisher<M>(…)->publish(m);`) and still have a live
470// publisher afterwards. `Owned<T>` is SOLE ownership, so there is no second
471// reference to keep: the entity is destroyed when the holder is. For the
472// expression above that is still correct — the temporary outlives the
473// full-expression — and for the member pattern the corpus actually uses
474// (`publisher_ = this->create_publisher<M>(…)`) it is identical to upstream,
475// where the node is the last owner. What is gone is a publisher that survives
476// its own handle, which upstream does not give either once the node drops it.
477//
478// phase-456 W4 measured that the node was never an ARENA owner here: nothing
479// registers a publisher and `EntryKind` has no publisher kind, so the retention
480// was retention and nothing more. The comment that used to sit on it claimed
481// the arena stored `&entity`, which was false in both halves and is the kind of
482// copied rationale that teaches the next reader the wrong model.
483namespace rclcpp {
484template <typename M>
486 const ::rclcpp::QoS& qos) {
489 topic);
490 return typename Publisher<M>::SharedPtr(::rclcpp::tr::forward_rvalue(p));
491}
492
493template <typename M>
494inline typename Publisher<M>::SharedPtr Node::create_publisher(const char* topic, ::size_t depth) {
495 return this->create_publisher<M>(topic, ::rclcpp::QoS(static_cast<uint32_t>(depth)));
496}
497} // namespace rclcpp
498
499// The `std::string`-keyed forwarders, then the `Node::SharedPtr`-taking
500// verbs. phase-476 W3 split what was one block behind all four capability
501// probes: each half now names the one capability its signatures use. Every
502// verb here forwards to (or refuses in place of) a freestanding form defined
503// after these blocks.
504#if defined(NROS_CPP_HAS_STD_STRING) // hosted-family: string-interop
505
506namespace rclcpp {
507
508namespace detail {
509
510// phase-426 W4 — `adopt_executor_param_seed` IS GONE, along with the store it
511// existed to reconcile.
512//
513// It re-read every launch-seeded parameter out of the EXECUTOR's store and
514// copied it over the code default, because `rclcpp::Node`'s own
515// `nros::ParameterServer` was a DIFFERENT object and would otherwise have
516// answered 0.15 while launch said 0.03 (issue 0745). `nros::ComponentNode`
517// carried the same dispatch written a second time, in C++17 `if constexpr`,
518// and both headers flagged the duplication against themselves.
519//
520// With one store there is nothing to reconcile: `declare_parameter` declares
521// into the store the seed was written to, finds the name already present, and
522// reads back the seeded value. The adoption is the FFI's `ALREADY_EXISTS`
523// path, not a helper — and it is now one path rather than two that could
524// disagree.
525
526} // namespace detail
527
528// --- Node ---------------------------------------------------------------------
529//
530// phase-427 W1-W3 — THE NODE TYPES ARE MERGED. `rclcpp::Node` stopped being a
531// separate adapter class here (it became an alias for `nros::Node`, and since
532// phase-483 W1 it IS the one class), and the hosted call shape that adapter
533// carried is now a set of overloads on that one type (declared in `node.hpp`,
534// defined below).
535//
536// What that closes, in order of how much it cost:
537//
538// * THE DUPLICATE PARAMETER FACADE. `nros.hpp` said so about itself ("KNOWN
539// DUPLICATION... There should be ONE helper"): the same rclcpp-shaped
540// `declare_parameter<T>` existed twice, once in C++14 here and once in
541// C++17 `if constexpr` on `ComponentNode`. One node type is what makes one
542// facade possible.
543// * THE `get_logger()` COLLISION. Two accessors with identical signatures
544// and different observable behaviour — a real node logger and the
545// hardcoded `"nros.compat"` sentinel. One type can hold only one, so the
546// merge forced the decision W5 records.
547// * THE TWO-VOCABULARY SPLIT. A ported file wrote `rclcpp::Node` and got a
548// type with no graph queries, no lifecycle, no callback groups, no action
549// entities and no out-ref creators; a native file wrote `nros::Node` and
550// got no `shared_ptr` creators and no parameters. Neither list was a
551// design; both were what the other file happened to have.
552//
553// The class holds no dispatch state and runs no loop — every entity it creates
554// is registered on the executor arena `rclcpp::init()` opened (issue 0465, one
555// session per image), which is all RFC-0019 permits a wrapper to be.
556//
557// Threading: callbacks fire on whatever thread services a spin verb, mirroring
558// the rclcpp default. A node cannot move between executors (RFC-0002, one
559// executor per RTOS task), which is why the executor is decided at construction
560// and never afterwards.
561
562} // namespace rclcpp
563
564namespace rclcpp {
565
566// The HOSTED half of `rclcpp::Node`, defined here rather than in `node.hpp`
567// because every body below needs a complete entity type (`Publisher<M>`,
568// `Subscription<M>`, `Service<S>`, `Client<S>`) or a helper the umbrella pulls
569// in (`create_subscription_raw` from `component.hpp`, the callback cells from
570// `subscription.hpp` / `timer.hpp`). That is the same split Phase 84.G8 already
571// uses for the out-ref `Node::create_X<>` templates, which live in the header
572// that owns each entity.
573//
574// The declarations — with the default arguments — are in `node.hpp` under
575// `NROS_CPP_HAS_STD_STRING`, the one predicate both files share.
576
577// -- publishers ---------------------------------------------------------------
578
579} // namespace rclcpp
580
581namespace rclcpp {
582template <typename M>
583inline typename Publisher<M>::SharedPtr Node::create_publisher(const ::std::string& topic,
584 const ::rclcpp::QoS& qos) {
585 return this->create_publisher<M>(topic.c_str(), qos);
586}
587} // namespace rclcpp
588
589namespace rclcpp {} // namespace rclcpp
590
591namespace rclcpp {
592template <typename M>
593inline typename Publisher<M>::SharedPtr Node::create_publisher(const ::std::string& topic,
594 ::size_t depth) {
595 return this->create_publisher<M>(topic.c_str(), ::rclcpp::QoS(static_cast<uint32_t>(depth)));
596}
597} // namespace rclcpp
598
599namespace rclcpp {
600
601// -- subscriptions ------------------------------------------------------------
602//
603// phase-417 — ONE DISPATCH PATH. This ARENA-REGISTERS the subscription through
604// `rclcpp::create_subscription_raw` (`component.hpp`), the same
605// `nros_cpp_subscription_register` call the native callback-style
606// `create_subscription` makes, so the executor owns the subscriber and
607// dispatches the callback during `spin_once` — whichever spin verb the caller
608// drives. It used to create a POLL-mode subscription and drain it from a
609// node-local `pump()`, which only `rclcpp::spin` / `spin_some` called: a file
610// that spun `rclcpp::spin_once()` instead got zero callbacks and no diagnostic.
611//
612// Why not the out-ref `create_subscription(sub, topic, qos, cb)` overload: that
613// one is SFINAE-restricted to `void(*)(const M&)` — a plain function pointer
614// with NO context slot — and every ported rclcpp callback captures.
615//
616// The receive-buffer hint is `rx_buffer_capacity<M>` — the same number the poll
617// path's `take()` stacks — and NOT the strict `rx_size_bound<M>`, whose
618// unbounded-type arm is a deliberate compile error (issue 0964). A ported file
619// must not stop compiling because its message has an unbounded string.
620//
621// ONE THING THIS LOSES, stated rather than left silent:
622// `create_subscription_raw` hardcodes an EMPTY type hash (it takes no such
623// parameter), which `normalize_type_hash` turns into `"TypeHashNotSupported"`.
624// It does not affect DELIVERY — a subscriber's data keyexpr puts `*` in the
625// hash slot — but under `ros-iron` / `ros-jazzy` the subscription's LIVELINESS
626// token advertises the placeholder instead of the real hash, so
627// `ros2 topic info --verbose` reads differently.
628//
629// WHAT THE RETURNED HANDLE IS: a keep-alive, which is all upstream source does
630// with it (`rclcpp::Subscription<M>::SharedPtr sub_;`). The executor owns the
631// real subscriber. This comment used to say `sub->take(msg)` on it "answers
632// `NotInitialized`"; it did not — `initialized_` was true and the call reached
633// `&mut *(storage as *mut RmwSubscriber)` over bytes the arena never filled.
634// phase-456 W2 made the handle a type with no `operator->`, and W2b moved the
635// taking API to `rclcpp::PollSubscription<M>`, so neither half of that sentence
636// has a subject any more.
637} // namespace rclcpp
638
639namespace rclcpp {
640template <typename M, typename Cb>
641inline typename Subscription<M>::SharedPtr
642Node::create_subscription(const ::std::string& topic, const ::rclcpp::QoS& qos, Cb cb) {
643 // phase-476 W1 — forwards to the freestanding `const char*` overload.
644 return this->create_subscription<M>(topic.c_str(), qos, ::rclcpp::tr::forward_rvalue(cb));
645}
646} // namespace rclcpp
647
648namespace rclcpp {} // namespace rclcpp
649
650namespace rclcpp {
651template <typename M, typename Cb>
652inline typename Subscription<M>::SharedPtr Node::create_subscription(const ::std::string& topic,
653 ::size_t depth, Cb cb) {
654 return this->create_subscription<M>(topic.c_str(), depth, ::rclcpp::tr::forward_rvalue(cb));
655}
656} // namespace rclcpp
657
658namespace rclcpp {
659
660// -- wall timer ---------------------------------------------------------------
661
662// phase-476 W2 — `create_wall_timer` is defined after the hosted block now
663// (`-- timers --`): it returns a `TimerHandle` and allocates nothing, so it is
664// not a hosted capability.
665
666// -- parameters ---------------------------------------------------------------
667//
668// MOVED OUT OF THIS BLOCK (phase-426 W4). The definitions are below, after
669// `#endif // NROS_CPP_HAS_SHARED_PTR && ...`, because a node's parameters are
670// not a hosted capability: the scalar FFI is `<cstdint>` and a `const char*`,
671// and `rclcpp::Seq<T, N>` carries arrays with no STL either. Only the
672// `std::string`-KEYED overloads (declared on the class in `node.hpp`) need the
673// STL, and only those keep the gate.
674//
675// `parameters()` IS GONE (phase-426 W4). It handed out a reference to the node's
676// own `nros::ParameterServer`, described as the escape hatch for "the C-API
677// helpers that take an `nros_parameter_server_t*`" - and no such helper
678// existed: `nros_executor_register_parameter_services` takes the executor,
679// never a standalone store. That class is deleted now too, so there is nothing
680// to return and nothing that took it. A node's parameters are reached through
681// `declare_parameter` / `get_parameter` / `set_parameter`, which name the store
682// the services actually read.
683
684// -- services and clients -----------------------------------------------------
685//
686// A callback of upstream's `shared_ptr<Request>, shared_ptr<Response>` shape is
687// REFUSE-LOUD rather than "no matching function": that signature needs a
688// per-request heap allocation on the delivery path, which is a second delivery
689// path, not a spelling.
690
691} // namespace rclcpp
692
693namespace rclcpp {
694template <typename S>
695inline ::rclcpp::Owned<::rclcpp::PollService<S>> Node::create_service(const ::std::string& name,
696 const ::rclcpp::QoS& qos) {
697 // phase-476 W1 — forwards to the freestanding `const char*` overload.
698 return this->template create_service<S>(name.c_str(), qos);
699}
700} // namespace rclcpp
701
702namespace rclcpp {} // namespace rclcpp
703
704namespace rclcpp {
705template <typename S, typename F, typename>
706inline typename Service<S>::SharedPtr Node::create_service(const ::std::string& name, F callback,
707 const ::rclcpp::QoS& qos) {
708 return this->template create_service<S>(name.c_str(), callback, qos);
709}
710} // namespace rclcpp
711
712namespace rclcpp {} // namespace rclcpp
713
714namespace rclcpp {
715template <typename S, typename F, typename, typename>
716inline typename Service<S>::SharedPtr Node::create_service(const ::std::string&, F,
717 const ::rclcpp::QoS&) {
718 static_assert(::rclcpp::detail::refuse<F>::value,
719 NROS_RCLCPP_REFUSE_SHARED_PTR_SERVICE_CALLBACK);
720 return typename Service<S>::SharedPtr();
721}
722} // namespace rclcpp
723
724namespace rclcpp {} // namespace rclcpp
725
726namespace rclcpp {
727template <typename S>
728inline ::rclcpp::Owned<::rclcpp::PollClient<S>> Node::create_client(const ::std::string& name,
729 const ::rclcpp::QoS& qos) {
730 // phase-476 W1 — forwards to the freestanding `const char*` overload.
731 return this->template create_client<S>(name.c_str(), qos);
732}
733} // namespace rclcpp
734
735namespace rclcpp {} // namespace rclcpp
736
737namespace rclcpp {
738template <typename S, typename F, typename>
739inline typename Client<S>::SharedPtr Node::create_client(const ::std::string& name, F callback,
740 const ::rclcpp::QoS& qos) {
741 return this->template create_client<S>(name.c_str(), callback, qos);
742}
743} // namespace rclcpp
744
745namespace rclcpp {} // namespace rclcpp
746
747namespace rclcpp {
748template <typename S, typename F, typename, typename>
749inline typename Client<S>::SharedPtr Node::create_client(const ::std::string&, F,
750 const ::rclcpp::QoS&) {
751 static_assert(::rclcpp::detail::refuse<F>::value,
752 NROS_RCLCPP_REFUSE_SHARED_PTR_SERVICE_CALLBACK);
753 return typename Client<S>::SharedPtr();
754}
755} // namespace rclcpp
756
757namespace rclcpp {} // namespace rclcpp
758
759#endif // NROS_CPP_HAS_STD_STRING
760
761#if defined(NROS_CPP_HAS_SHARED_PTR) // hosted-family: shared-ptr-interop
762namespace rclcpp {
763
764// `rclcpp::Node` is defined in `node.hpp`, UNCONDITIONALLY, not here, because
765// a freestanding target gets the class and must get its ROS 2 spelling with it.
766//
767// phase-417 — `pump()` IS GONE and does not come back. It ran a node's wall
768// timers and drained its polling subscriptions, and only `rclcpp::spin` /
769// `spin_some` called it, so a node driven by any other spin verb dispatched
770// nothing. Every entity a node creates is registered on the executor arena, so
771// there is nothing left for a node-local sweep to do and mixing spin spellings
772// is harmless. A second dispatch path here is the RFC-0019 violation the whole
773// item was about, and it cannot be made visible by a diagnostic.
774
775// --- rclcpp::create_timer (phase-430 W6) -------------------------------------
776//
777// Humble has NO `Node::create_timer` member — the clock-taking verb is a FREE
778// function, `rclcpp::create_timer(node, clock, period, callback[, group])`, and
779// that is the only form a Humble-era file can be written in. Ours takes the
780// same arguments in the same order and returns the same cell
781// `create_wall_timer` returns.
782//
783// The clock is `rclcpp::Clock*`, which is exactly what `node->get_clock()` hands
784// back, so the ported line binds with no conversion. Upstream's parameter is
785// `rclcpp::Clock::SharedPtr`; there is no allocator here (RFC-0022) and the
786// clock is a member of the node, so the pointer is the counterpart — the same
787// ADOPT-BOUNDED trade `get_clock()` itself makes.
788//
789// WHAT THE CLOCK DOES, and it is the whole point of the verb: a
790// `RCL_ROS_TIME` clock follows `/clock`, so the timer stops when a bag
791// stops and re-times when the bag is replayed at another rate; a
792// `RCL_STEADY_TIME` one does not. `create_wall_timer` is the steady
793// verb and is unaffected by simulated time. Upstream distinguishes the two
794// cases by TYPE (`WallTimer` vs `GenericTimer<ClockT>`) *and* by VERB; the type
795// distinction is unportable here (the clock is a runtime field on one flat
796// `Timer` — RFC-0089 §"Timer, studied against RTOS semantics"), the verb
797// distinction is portable and is what ported code writes, so that is what we
798// take.
799//
800// NOT ADDED: a clock-less `Node::create_timer(period, callback)`. That member
801// arrives in Iron, and the captured surface here is Humble; adding it now would
802// claim a spelling the recorded upstream does not have.
803
804namespace detail {
805
811template <typename N, typename ::rclcpp::tr::enable_if<
812 ::rclcpp::tr::is_convertible<N*, ::rclcpp::Node*>::value, int>::type = 0>
813inline ::rclcpp::Node& as_node_ref(const ::std::shared_ptr<N>& n) {
814 return *n;
815}
816
817} // namespace detail
818
819// phase-476 W2 — `rclcpp::create_timer` moved after this block (`-- timers --`).
820
821// --- spin / spin_some --------------------------------------------------------
822//
823// phase-417 — FORWARDERS, and nothing else. `rclcpp::spin(node)` is
824// `rclcpp::spin()` (block until `ok()` goes false) and `rclcpp::spin_some(node)`
825// is one 0-timeout `rclcpp::spin_once`. Both used to call a node-local `pump()`
826// first, which was the ONLY thing that ran a ported node's timers and
827// subscriptions; now those live on the executor, so these two dispatch nothing
828// of their own and a file that reaches for `rclcpp::spin_once()` or drives an
829// `rclcpp::Executor` gets exactly the same callbacks. That equivalence is the
830// structural prerequisite for step B: after it both node types share a name,
831// and a mismatch would be invisible.
832//
833// One behaviour change for an existing caller: `rclcpp::spin()` RETURNS on the
834// first failing `spin_once`, where this loop used to discard the result and
835// keep going. A dead session now ends the spin instead of looking alive
836// forever, which is what `rclcpp::spin()` and `Executor::spin` already promise.
837//
838// The `node` argument is still checked, and still otherwise unused: there is
839// one session per image (issue 0465), so every `rclcpp::Node` is on the global
840// executor these verbs drive. Upstream takes the node for the same reason and
841// spins the executor it belongs to.
842
843} // namespace rclcpp
844
845#endif // NROS_CPP_HAS_SHARED_PTR
846
847// --- spin verbs, freestanding (phase-482 W2) ----------------------------------
848//
849// They take `Node::SharedPtr`, which is `rclcpp::Handle<Node>` on every target
850// since phase-482 W2, so they left the `shared_ptr` block: a freestanding port's
851// `main` spells `rclcpp::spin(node)` exactly as a hosted one does.
852namespace rclcpp {
853
854inline void spin(const Node::SharedPtr& node) {
855 if (!node || !node->initialized()) {
856 return;
857 }
859}
860
861inline void spin_some(const Node::SharedPtr& node) {
862 if (!node || !node->initialized()) {
863 return;
864 }
866}
867
868// Future type is templated rather than `const auto& future` so the header
869// stays parseable under `-std=c++14` (the C++20 abbreviated-function-template
870// syntax breaks `just check cpp`'s freestanding probe).
871//
872// issue 0339 — the bounded branch used to call `Executor::spin(timeout_ms)`
873// and never consult the future, so it BURNED THE WHOLE TIMEOUT even when the
874// future completed on the first spin: a `wait_for_service` / `send_request`
875// sequence ported from rclcpp paid the full timeout on every SUCCESSFUL call.
876// The unbounded branch directly below already had the right shape; both now
877// share it, differing only in whether a deadline exists.
885template <typename Future>
887 const Future& future, int32_t timeout_ms = -1) {
888 if (!node || !node->initialized()) {
890 }
891 // Poll slice: same 10 ms the unbounded loop always used.
892 constexpr int32_t kPollMs = 10;
893 const bool bounded = timeout_ms >= 0;
895 const uint64_t budget_ns = bounded ? static_cast<uint64_t>(timeout_ms) * 1000000ull : 0ull;
896
897 while (::rclcpp::ok()) {
898 if (future.is_ready()) {
900 }
902 // Re-check before the deadline test: a future that became ready on the
903 // spin just above must report SUCCESS even if the budget expired in
904 // the same slice.
905 if (future.is_ready()) {
907 }
910 }
911 }
913}
914
915} // namespace rclcpp
916
917#if defined(NROS_CPP_HAS_SHARED_PTR) // hosted-family: shared-ptr-interop
918namespace rclcpp {
919
920// phase-482 W2 — the upstream `main` is `rclcpp::spin(std::make_shared<MyNode>())`,
921// a TEMPORARY `shared_ptr`. Binding it to a function parameter is safe (it lives
922// for the call), but converting it to a `Node::SharedPtr` handle is refused
923// because a STORED handle would dangle. These overloads take the `shared_ptr` by
924// reference instead, so that line compiles unchanged and owns the node for
925// exactly as long as the spin runs.
926template <typename N, typename ::rclcpp::tr::enable_if<
927 ::rclcpp::tr::is_convertible<N*, Node*>::value, int>::type = 0>
928inline void spin(const ::std::shared_ptr<N>& node) {
929 spin(Node::SharedPtr(node));
930}
931
932template <typename N, typename ::rclcpp::tr::enable_if<
933 ::rclcpp::tr::is_convertible<N*, Node*>::value, int>::type = 0>
934inline void spin_some(const ::std::shared_ptr<N>& node) {
936}
937
938template <
939 typename N, typename Future,
940 typename ::rclcpp::tr::enable_if<::rclcpp::tr::is_convertible<N*, Node*>::value, int>::type = 0>
941inline FutureReturnCode spin_until_future_complete(const ::std::shared_ptr<N>& node,
942 const Future& future, int32_t timeout_ms = -1) {
943 return spin_until_future_complete(Node::SharedPtr(node), future, timeout_ms);
944}
945
946} // namespace rclcpp
947#endif // NROS_CPP_HAS_SHARED_PTR
948
949// -- dispatch verbs, freestanding (phase-476 W1) -------------------------------
950//
951// The `const char*` overloads of `create_subscription` / `create_service` /
952// `create_client`. Each result is a handle or an owned poll object, none of them
953// allocates, and their bodies are the ones the hosted `std::string` overloads
954// used to carry; those overloads now forward here.
955
956namespace rclcpp {
957
958template <typename M, typename Cb>
959inline typename Subscription<M>::SharedPtr
960Node::create_subscription(const char* topic, const ::rclcpp::QoS& qos, Cb cb) {
961 // phase-456 W2 — the arena owns everything, so this allocates nothing.
962 //
963 // This used to `make_shared` a `detail::SubscriptionCallback<M>` cell whose
964 // only job was to give a capturing lambda a stable address, push it into
965 // `owned_entities` to keep it alive, and hand back a `shared_ptr` aliasing
966 // into it. W1 put the capture in the arena entry, so the cell has nothing
967 // left to hold; W2 stops pretending the result is a pointer to an object.
968 using Capture = ::rclcpp::InplaceFn<void(const M&)>;
969 Capture captured(::rclcpp::tr::forward_rvalue(cb));
970 ::size_t handle_id = 0;
971 const ::rclcpp::Result r = ::rclcpp::detail::register_subscription_capturing<M>(
972 *this, topic, qos, captured, &handle_id);
973 // phase-476 W2 — the arena's copy owns the callable now and destroys it on
974 // release; ours must not destroy it too.
975 if (r.ok()) {
976 captured.relinquish();
977 }
978 ::rclcpp::detail::require_created(r, "create_subscription", topic);
979 return typename Subscription<M>::SharedPtr(this->executor_handle(), handle_id);
980}
981
982template <typename M, typename Cb>
984 ::size_t depth, Cb cb) {
985 return this->create_subscription<M>(topic, ::rclcpp::QoS(static_cast<uint32_t>(depth)),
986 ::rclcpp::tr::forward_rvalue(cb));
987}
988
989template <typename S>
990inline ::rclcpp::Owned<::rclcpp::PollService<S>> Node::create_service(const char* name,
991 const ::rclcpp::QoS& qos) {
992 // phase-456 W5 — the POLL server, by value. `Service<S>::SharedPtr` is a
993 // dispatch handle now, and this factory exists precisely to be
994 // `->take_request()`'d, so it must hand back something dereferenceable.
995 // `Owned<PollService<S>>` is that and costs no allocator; the node keeps no
996 // second reference because there is no second reference to keep.
999 "create_service", name);
1000 return ::rclcpp::Owned<::rclcpp::PollService<S>>(::rclcpp::tr::forward_rvalue(s));
1001}
1002
1003template <typename S, typename F, typename>
1005 const ::rclcpp::QoS& qos) {
1006 // phase-456 W5 — the arena owns the server and (W3) the handler, so this
1007 // allocates nothing and there is no cell to retain. The `owned_entities`
1008 // push that used to stand here was LOAD-BEARING while the arena held
1009 // `&*s` — dropping the caller's pointer would have dangled it — and became
1010 // pure retention when W3 moved the context to the handler.
1011 Service<S> s;
1013 "create_service", name);
1014 return typename Service<S>::SharedPtr(this->executor_handle(), s.handle_id());
1015}
1016
1017template <typename S, typename F, typename, typename>
1018inline typename Service<S>::SharedPtr Node::create_service(const char*, F, const ::rclcpp::QoS&) {
1019 static_assert(::rclcpp::detail::refuse<F>::value,
1021 return typename Service<S>::SharedPtr();
1022}
1023
1024template <typename S>
1025inline ::rclcpp::Owned<::rclcpp::PollClient<S>> Node::create_client(const char* name,
1026 const ::rclcpp::QoS& qos) {
1027 // phase-456 W9 — the FUTURE-style client, by value. `Client<S>::SharedPtr`
1028 // is a dispatch handle now, and this factory exists precisely to be
1029 // `->send_request()`'d, so it must hand back something dereferenceable.
1030 // `Owned<PollClient<S>>` is that and costs no allocator; the node keeps no
1031 // second reference because there is no second reference to keep.
1034 "create_client", name);
1035 return ::rclcpp::Owned<::rclcpp::PollClient<S>>(::rclcpp::tr::forward_rvalue(c));
1036}
1037
1038template <typename S, typename F, typename>
1040 const ::rclcpp::QoS& qos) {
1041 // phase-456 W9 — the arena owns the client and (W3) the handler, so this
1042 // allocates nothing and there is no cell to retain. The `owned_entities`
1043 // push that used to stand here was LOAD-BEARING while the arena held
1044 // `&*c` — dropping the caller's pointer would have dangled it — and became
1045 // pure retention when W3 moved the context to the handler. Same removal W5
1046 // made one entity over.
1047 Client<S> c;
1049 "create_client", name);
1050 return typename Client<S>::SharedPtr(this->executor_handle(), c.handle_id());
1051}
1052
1053template <typename S, typename F, typename, typename>
1054inline typename Client<S>::SharedPtr Node::create_client(const char*, F, const ::rclcpp::QoS&) {
1055 static_assert(::rclcpp::detail::refuse<F>::value,
1057 return typename Client<S>::SharedPtr();
1058}
1059
1060} // namespace rclcpp
1061
1062// -- timers -------------------------------------------------------------------
1063//
1064// phase-476 W2 — FREESTANDING. The callable is copied into the executor arena
1065// (`nros_cpp_timer_create_capturing`), which destroys it when the timer is
1066// released, so these allocate nothing and return a two-word `TimerHandle`.
1067// Defined after the hosted block so that, on a hosted target, the
1068// `shared_ptr` member of `as_node_ref`'s overload set is already declared when
1069// `rclcpp::create_timer`'s body names it.
1070
1071namespace rclcpp {
1072namespace detail {
1073
1083template <typename Cb>
1085 Duration period, Cb&& cb, const char* verb) {
1086 using Capture = InplaceFn<void()>;
1087 Capture captured(tr::relay<Cb>(cb));
1088 // Non-capturing lambdas, so each decays to the plain function pointer the
1089 // arena stores. `ctx` is the ARENA's copy of `captured`, never ours.
1090 nros_cpp_timer_callback_t invoke = [](void* ctx) { (*static_cast<Capture*>(ctx))(); };
1091 nros_cpp_timer_callback_t destroy = [](void* ctx) { static_cast<Capture*>(ctx)->clear(); };
1092 const int64_t ns = period.nanoseconds();
1093 const uint64_t ms = ns > 0 ? static_cast<uint64_t>(ns / 1000000) : uint64_t(0);
1094 size_t handle_id = 0;
1095 const nros_cpp_node_t* h = node.ffi_handle();
1096 Result r = h == nullptr ? Result(ErrorCode::NotInitialized)
1098 h, static_cast<uint8_t>(clock_type), ms, invoke,
1099 reinterpret_cast<const uint8_t*>(&captured), sizeof(Capture),
1100 destroy, &handle_id));
1101 if (r.ok()) {
1102 captured.relinquish();
1103 }
1104 // A null return would be silent HERE above all: a ported node stores the
1105 // timer handle and never dereferences it, so a dead timer would simply
1106 // never fire.
1108 return TimerHandle(node.executor_handle(), handle_id);
1109}
1110
1111} // namespace detail
1112} // namespace rclcpp
1113
1114namespace rclcpp {
1115
1116template <typename Cb>
1117inline ::rclcpp::TimerHandle Node::create_wall_timer(::rclcpp::Duration period, Cb cb) {
1118 return ::rclcpp::detail::register_timer_capturing(
1119 *this, RCL_STEADY_TIME, period, ::rclcpp::tr::forward_rvalue(cb), "create_wall_timer");
1120}
1121
1122#ifdef NROS_CPP_HAS_STD_CHRONO // hosted-family: chrono-interop
1123template <typename Rep, typename Period, typename Cb>
1124inline ::rclcpp::TimerHandle Node::create_wall_timer(::std::chrono::duration<Rep, Period> period,
1125 Cb cb) {
1126 const auto ns = ::std::chrono::duration_cast<::std::chrono::nanoseconds>(period).count();
1127 return this->create_wall_timer(::rclcpp::Duration::from_nanoseconds(static_cast<int64_t>(ns)),
1128 ::rclcpp::tr::forward_rvalue(cb));
1129}
1130#endif // NROS_CPP_HAS_STD_CHRONO
1131
1132namespace detail {
1137inline ::rclcpp::Node& as_node_ref(const ::rclcpp::Node::SharedPtr& n) {
1138 return *n;
1139}
1140inline ::rclcpp::Node& as_node_ref(::rclcpp::Node& n) {
1141 return n;
1142}
1143inline ::rclcpp::Node& as_node_ref(::rclcpp::Node* n) {
1144 return *n;
1145}
1146} // namespace detail
1147
1155template <typename NodeT, typename CallbackT>
1156inline ::rclcpp::TimerHandle create_timer(NodeT&& node, ::rclcpp::Clock* clock,
1159 const rcl_clock_type_t type =
1160 clock != nullptr ? clock->get_clock_type() : n.get_clock()->get_clock_type();
1161 return ::rclcpp::detail::register_timer_capturing(
1162 n, type, period, ::rclcpp::tr::relay<CallbackT>(callback), "create_timer");
1163}
1164
1165#ifdef NROS_CPP_HAS_STD_CHRONO // hosted-family: chrono-interop
1171template <typename NodeT, typename Rep, typename Period, typename CallbackT>
1172inline ::rclcpp::TimerHandle create_timer(NodeT&& node, ::rclcpp::Clock* clock,
1173 ::std::chrono::duration<Rep, Period> period,
1174 CallbackT&& callback) {
1175 const auto ns = ::std::chrono::duration_cast<::std::chrono::nanoseconds>(period).count();
1176 return create_timer(::rclcpp::tr::relay<NodeT>(node), clock,
1177 ::rclcpp::Duration::from_nanoseconds(static_cast<int64_t>(ns)),
1178 ::rclcpp::tr::relay<CallbackT>(callback));
1179}
1180#endif // NROS_CPP_HAS_STD_CHRONO
1181
1182} // namespace rclcpp
1183
1184// --- node parameters ---------------------------------------------------------
1185//
1186// Out here rather than in the hosted block above: see the note at the
1187// `-- parameters --` marker there, and `node.hpp` for why the gate moved to
1188// the `std::string`-keyed overloads alone (phase-426 W4).
1189
1190// -- parameters ---------------------------------------------------------------
1191//
1192// Forwarders onto THE parameter store — the `nros_params::ParameterServer` the
1193// EXECUTOR owns, reached through `rclcpp/node_parameters.hpp`. There is no other
1194// one any more.
1195//
1196// What changed in phase-426 W4, and why it is not a detail: these used to
1197// forward to an inline `nros::ParameterServer<NROS_RCLCPP_MAX_PARAMS>` member
1198// on the hosted block — a second store, node-local, which the six
1199// `rcl_interfaces/srv/*` servers could not read. So a parameter declared here
1200// was invisible to `ros2 param get`, a sibling node did not share it, and the
1201// launch seed had to be copied across by a helper because the two stores could
1202// not be the same object. The member is deleted; the seam is one FFI call;
1203// `ros2 param get <node> <name>` sees what `declare_parameter` wrote. Issue
1204// 0793 / RFC-0089 §"Parameters".
1205//
1206// ADOPT-BOUNDED still, and the envelope is now about TYPES rather than scope:
1207// bool / int / int64_t / double reach the store, `std::string` and
1208// `std::vector<T>` do under `NROS_CPP_STD`, and rclcpp's `ParameterDescriptor`
1209// / `ignore_override` / callback arguments remain absent (the
1210// compile-time-options rule). The WIRE half of the acceptance — that the six
1211// services answer per node FQN — is phase-426 W3/W6.
1212//
1213// Where the image declares no `param_services` capability there is no store at
1214// all, and every call answers `ErrorCode::Unsupported`. This facade wears
1215// upstream's value-returning signature, which has nowhere to report that, so
1216// `declare_parameter` returns the code default. The loud half USED to be
1217// `nros::ComponentNode`, whose own facade recorded the failure on an `ok()`
1218// flag and made it boot-fatal; phase-427 W4 deleted that type, so no C++ path
1219// is boot-fatal on a missing store any more. `rclcpp::Node` still carries the
1220// flag (`set_error` / `ok()`); routing the parameter path back onto it is a
1221// separate decision, because it changes what an upstream-shaped
1222// `declare_parameter` does.
1223
1232
1233namespace rclcpp {
1234template <typename T> inline T Node::declare_parameter(const char* name, T default_value) {
1235 // phase-446 W6 -- a declaration the contract's `params:` does not make, or
1236 // makes with another type, refuses the boot through `set_error` before
1237 // anything reaches the store (see `Node::check_declared_param`).
1238 if (!this->check_declared_param(name, ::rclcpp::detail::node_param_type<T>::value)) {
1239 return default_value;
1240 }
1241 const ::nros_cpp_node_t* h = this->ffi_handle();
1242 Result r = ::rclcpp::detail::node_param_declare(h, name, default_value);
1243 if (!r.ok() && r.raw() != RCLC_RET_ALREADY_EXISTS) {
1244 return default_value;
1245 }
1246 T out = T();
1247 if (!::rclcpp::detail::node_param_get(h, name, out).ok()) {
1248 return default_value;
1249 }
1250 return out;
1251}
1252} // namespace rclcpp
1253
1254namespace rclcpp {} // namespace rclcpp
1255
1256namespace rclcpp {
1257template <typename T> inline bool Node::get_parameter(const char* name, T& out) const {
1258 return ::rclcpp::detail::node_param_get(this->ffi_handle(), name, out).ok();
1259}
1260} // namespace rclcpp
1261
1262namespace rclcpp {} // namespace rclcpp
1263
1264namespace rclcpp {
1265template <typename T> inline T Node::get_parameter(const char* name) const {
1266 T out = T();
1267 (void)::rclcpp::detail::node_param_get(this->ffi_handle(), name, out);
1268 return out;
1269}
1270} // namespace rclcpp
1271
1272namespace rclcpp {
1273
1278} // namespace rclcpp
1279
1280namespace rclcpp {
1281template <typename T> inline Result Node::set_parameter(const char* name, T value) {
1282 return ::rclcpp::detail::node_param_set(this->ffi_handle(), name, value);
1283}
1284} // namespace rclcpp
1285
1286namespace rclcpp {} // namespace rclcpp
1287
1288namespace rclcpp {
1289inline bool Node::has_parameter(const char* name) const {
1290 return ::rclcpp::detail::node_param_has(this->ffi_handle(), name);
1291}
1292} // namespace rclcpp
1293
1294// --- the rest of rclcpp's parameter surface (phase-417 W4.a) -----------------
1295//
1296// Every one of these is a forwarder onto the executor's ONE store through
1297// `rclcpp/node_parameters.hpp`. None of them holds state; a wrapper that did
1298// would be the second store phase-426 removed, in a smaller spelling.
1299
1300namespace rclcpp {
1301
1302template <typename T>
1306 // The descriptor is attached AFTER the declaration, which is the store's
1307 // own shape (rclc's `rclc_add_parameter_description` & co.). A failure
1308 // here leaves the parameter declared with its value, which is what
1309 // upstream does when the descriptor is consistent with the default — and
1310 // when it is not, the attach refuses rather than advertising a bound the
1311 // value breaks.
1312 (void)::rclcpp::detail::node_param_apply_descriptor(
1313 this->ffi_handle(), name, descriptor, ::rclcpp::detail::node_param_type<T>::value);
1314 return in_effect;
1315}
1316
1317template <typename T>
1318inline bool Node::get_parameter_or(const char* name, T& out, T fallback) const {
1319 if (::rclcpp::detail::node_param_get(this->ffi_handle(), name, out).ok()) {
1320 return true;
1321 }
1322 out = fallback;
1323 return false;
1324}
1325
1327 return ::rclcpp::detail::node_param_undeclare(this->ffi_handle(), name);
1328}
1329
1331 int32_t code = 0;
1332 if (!::rclcpp::detail::node_param_get_type(this->ffi_handle(), name, code).ok()) {
1333 return PARAMETER_NOT_SET;
1334 }
1335 return static_cast<ParameterType>(code);
1336}
1337
1338inline Result Node::get_parameter_types(const char* const* names, ::size_t count,
1339 ParameterType* out) const {
1340 if ((names == nullptr || out == nullptr) && count != 0) {
1342 }
1343 for (::size_t i = 0; i < count; ++i) {
1344 out[i] = this->get_parameter_type(names[i]);
1345 }
1346 return Result(RCL_RET_OK);
1347}
1348
1350 ::size_t text_len) const {
1352 // The buffer is split in half, one NUL-terminated string in each, so ONE
1353 // caller buffer serves both texts and neither can run into the other.
1354 char* description = nullptr;
1355 char* constraints = nullptr;
1356 ::size_t half = 0;
1357 if (text != nullptr && text_len >= 2) {
1358 half = text_len / 2;
1359 description = text;
1360 constraints = text + half;
1361 }
1362 int32_t type = 0;
1363 Result r = ::rclcpp::detail::node_param_describe(this->ffi_handle(), name, description, half,
1364 constraints, half, &out.read_only, &type);
1365 if (r.raw() == RCLC_RET_NOT_FOUND) {
1366 return r;
1367 }
1368 out.description = description;
1369 out.additional_constraints = constraints;
1370
1371 // The range, read through whichever accessor the TYPE picks. `NOT_FOUND`
1372 // from it means "no range", which is not a failure of `describe`.
1373 if (type == ::rclcpp::param_type::INTEGER) {
1374 out.has_range =
1375 ::rclcpp::detail::node_param_get_range(this->ffi_handle(), name, &out.integer_from,
1376 &out.integer_to, &out.integer_step)
1377 .ok();
1378 } else if (type == ::rclcpp::param_type::DOUBLE) {
1379 out.has_range =
1380 ::rclcpp::detail::node_param_get_range(this->ffi_handle(), name, &out.double_from,
1381 &out.double_to, &out.double_step)
1382 .ok();
1383 }
1384 return r;
1385}
1386
1387inline Result Node::list_parameters(const char* prefix, char* out_names, ::size_t name_stride,
1388 ::size_t max_names, ::size_t& count) const {
1389 return ::rclcpp::detail::node_param_list(this->ffi_handle(), prefix, out_names, name_stride,
1390 max_names, &count);
1391}
1392
1394 return ::rclcpp::detail::node_params_set_atomically(this->ffi_handle(), writes, count);
1395}
1396
1398 void* context,
1400 return ::rclcpp::detail::node_param_add_on_set_callback(this->ffi_handle(), callback, context,
1401 &out_handle);
1402}
1403
1405 return ::rclcpp::detail::node_param_remove_on_set_callback(this->ffi_handle(), handle);
1406}
1407
1408} // namespace rclcpp
1409
1410// --- Rate / WallRate (phase-417 W2.d) ----------------------------------------
1411//
1412// A FORWARDER onto `rclcpp::spin(remaining_ms, poll_ms)`, which budgets by wall
1413// clock inside `nros_cpp_spin_for` — Rust-side, once.
1414//
1415// The obvious fifteen-line class with its own sleep loop is NOT admissible
1416// here, and the distinction is the whole point of this item: a loop in the
1417// wrapper that spins the executor is RFC-0020 violation class 2, and a wrapper
1418// that BLOCKS without spinning is what RFC-0021 forbids. Same capability, and
1419// only one of the two shapes is allowed. So `sleep()` computes a deadline and
1420// makes exactly one call into the entry point that already exists.
1421//
1422// ADOPT-BOUNDED, and the envelope is load-bearing enough that a ported loop
1423// behaves DIFFERENTLY here even though it compiles unchanged:
1424//
1425// * **Your callbacks RUN during the sleep.** `rclcpp::spin` drives the
1426// executor, so subscriptions and timers registered on the arena fire while
1427// `rate.sleep()` is blocked. Under a single-threaded rclcpp executor they
1428// do not — `Rate::sleep()` there is a pure `sleep_for`, and callbacks only
1429// run when you call `spin_some`. The nano-ros behaviour is the one a
1430// `while (ok()) { work(); rate.sleep(); }` loop usually WANTS (it is why
1431// that loop needs a second thread upstream), but it is not the same
1432// contract, and a callback that races your loop body is the way it shows.
1433// Since phase-417 that includes a `rclcpp::Node`'s OWN timers and
1434// subscriptions: they are executor entities now, so `rate.sleep()` runs
1435// them.
1436// * `Rate` and `WallRate` are the SAME clock. Upstream's `Rate` measures ROS
1437// time and `WallRate` steady time; both here read the monotonic
1438// `nros_cpp_time_ns()`, so a `Rate` does not slow down under a sim clock.
1439// `WallRate` is faithful; `Rate` is `WallRate` under a second name.
1440// * Resolution is ONE MILLISECOND — the FFI budget is `uint32_t` ms — and the
1441// remaining time is rounded UP, so a period below 1 ms cannot be met and a
1442// rate faster than 1 kHz is not expressible.
1443// * Before `rclcpp::init()` there is no executor to spin, so `sleep()` returns
1444// immediately rather than blocking. A rate loop that runs before init busy
1445// spins; upstream would have slept.
1446//
1447// Gated on `<chrono>` alone: the duration constructor and `period()` are spelled
1448// in `std::chrono`, and everything else it touches is an integer.
1449// phase-442 W5 — `Rate` EXISTS ON EVERY TARGET.
1450//
1451// It used to sit inside `#ifdef NROS_CPP_HAS_STD_CHRONO`, which made the whole
1452// type a property of the toolchain: a node written against `rclcpp::Rate`
1453// compiled on a host and vanished on ThreadX. Measured, the gate was paying for
1454// two members out of five — `Rate(double)`, `sleep()` and `reset()` are integer
1455// arithmetic over `nros_cpp_time_ns()` and never needed `<chrono>` at all.
1456//
1457// The two that did are handled differently, and the difference is the rule
1458// RFC-0096 D1 states. `period()` returns `rclcpp::Duration`, which is ours and
1459// exists everywhere, so the type's SHAPE no longer follows a probe. The
1460// `std::chrono` CONSTRUCTOR stays behind the capability macro, which is a gate
1461// on a METHOD — permitted, since `sizeof(Rate)` is `int64_t` + `uint64_t` in
1462// every configuration and two translation units cannot disagree about it.
1463//
1464// That constructor is the one thing here W8 still has to resolve: its parameter
1465// names `std::chrono::duration`, and W8's acceptance is zero `NROS_CPP_HAS_*` in
1466// the tree. Recorded rather than quietly left, because a gated method is exactly
1467// the kind of residue that reads as finished work.
1468
1469namespace rclcpp {
1470
1473class Rate {
1474 public:
1477 explicit Rate(double frequency_hz)
1478 : period_ns_(frequency_hz > 0.0 ? static_cast<int64_t>(1000000000.0 / frequency_hz) : 0) {
1479 reset();
1480 }
1481
1484 explicit Rate(::rclcpp::Duration period) : period_ns_(period.nanoseconds()) {
1485 if (period_ns_ < 0) period_ns_ = 0;
1486 reset();
1487 }
1488
1489#ifdef NROS_CPP_HAS_STD_CHRONO // hosted-family: chrono-interop
1495 template <typename Rep, typename Period>
1496 explicit Rate(std::chrono::duration<Rep, Period> period)
1497 : period_ns_(std::chrono::duration_cast<std::chrono::nanoseconds>(period).count()) {
1498 if (period_ns_ < 0) period_ns_ = 0;
1499 reset();
1500 }
1501#endif
1502
1508 bool sleep() {
1509 if (period_ns_ <= 0) {
1510 return false;
1511 }
1513 if (now_ns >= next_tick_ns_) {
1514 // Overran. Re-anchor on now rather than firing a burst to catch up
1515 // — upstream's `Rate` contract, and the choice the retired
1516 // node-local `pump()` used to make for wall timers before they
1517 // became executor timers (which catch up instead; see the envelope
1518 // on `rclcpp::Timer`).
1519 next_tick_ns_ = now_ns + static_cast<uint64_t>(period_ns_);
1520 return false;
1521 }
1522 const uint64_t remaining_ns = next_tick_ns_ - now_ns;
1523 // Round UP: truncating a 0.4 ms remainder to 0 would turn `sleep()`
1524 // into a busy loop.
1525 uint32_t remaining_ms = static_cast<uint32_t>((remaining_ns + 999999ull) / 1000000ull);
1526 if (remaining_ms == 0) remaining_ms = 1;
1527 next_tick_ns_ += static_cast<uint64_t>(period_ns_);
1530 return true;
1531 }
1532
1534 void reset() { next_tick_ns_ = nros_cpp_time_ns() + static_cast<uint64_t>(period_ns_); }
1535
1542 ::rclcpp::Duration period() const { return ::rclcpp::Duration::from_nanoseconds(period_ns_); }
1543
1544 private:
1545 int64_t period_ns_;
1546 uint64_t next_tick_ns_;
1547};
1548
1552
1553} // namespace rclcpp
1554
1555#endif // NROS_RCLCPP_RCLCPP_HPP
Definition node.hpp:218
Definition client.hpp:129
::rclcpp::ClientHandle< S > SharedPtr
Definition client.hpp:141
size_t handle_id() const
Executor arena slot for the registration; SIZE_MAX until registered.
Definition client.hpp:322
Definition future.hpp:70
Definition node.hpp:703
const nros_cpp_node_t * ffi_handle() const
Definition node.hpp:1475
Result get_parameter_types(const char *const *names, ::size_t count, ParameterType *out) const
Definition rclcpp.hpp:1338
::rclcpp::Owned<::rclcpp::PollService< S > > create_service(const char *name, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
bool get_parameter(const char *name, T &out) const
Definition rclcpp.hpp:1257
typename::rclcpp::Subscription< M >::SharedPtr create_subscription(const char *topic, const ::rclcpp::QoS &qos, Cb cb)
typename::rclcpp::Publisher< M >::SharedPtr create_publisher(const char *topic, const ::rclcpp::QoS &qos=::rclcpp::QoS::default_profile())
create_publisher<M>(topic, qos) — available on every target.
::rclcpp::TimerHandle create_wall_timer(::rclcpp::Duration period, Cb cb)
bool check_declared_param(const char *name, int passed_type)
Definition node.hpp:2425
Result describe_parameter(const char *name, ParameterDescriptor &out, char *text, ::size_t text_len) const
Definition rclcpp.hpp:1349
T declare_parameter(const char *name, T default_value=T())
rclcpp::Node::declare_parameter<T>(name, default).
Definition rclcpp.hpp:1234
bool get_parameter_or(const char *name, T &out, T fallback) const
Definition rclcpp.hpp:1318
ParameterType get_parameter_type(const char *name) const
Definition rclcpp.hpp:1330
bool ok() const
Definition node.hpp:819
bool has_parameter(const char *name) const
rclcpp::Node::has_parameter(name).
Definition rclcpp.hpp:1289
void * executor_handle() const
Definition node.hpp:1499
Result list_parameters(const char *prefix, char *out_names, ::size_t name_stride, ::size_t max_names, ::size_t &count) const
Definition rclcpp.hpp:1387
::rclcpp::Handle< Node > SharedPtr
Definition node.hpp:903
Result remove_on_set_parameters_callback(ParameterCallbackHandle handle)
rclcpp::Node::remove_on_set_parameters_callback(handle).
Definition rclcpp.hpp:1404
Result set_parameters_atomically(const ParameterWrite *writes, ::size_t count)
Definition rclcpp.hpp:1393
::rclcpp::Owned<::rclcpp::PollClient< S > > create_client(const char *name, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
Result set_parameter(const char *name, T value)
Definition rclcpp.hpp:1281
Result undeclare_parameter(const char *name)
Definition rclcpp.hpp:1326
Result add_on_set_parameters_callback(OnSetParametersCallbackType callback, void *context, ParameterCallbackHandle &out_handle)
Definition rclcpp.hpp:1397
Definition executor.hpp:88
Definition executor.hpp:81
::rclcpp::Owned< Publisher< M > > SharedPtr
Definition publisher.hpp:164
Definition qos.hpp:237
Definition rclcpp.hpp:1473
bool sleep()
Definition rclcpp.hpp:1508
Rate(::rclcpp::Duration period)
Definition rclcpp.hpp:1484
Rate(double frequency_hz)
Definition rclcpp.hpp:1477
void reset()
rclcpp::Rate::reset() — anchor the next tick one period from now.
Definition rclcpp.hpp:1534
::rclcpp::Duration period() const
Definition rclcpp.hpp:1542
Definition result.hpp:216
static constexpr ResultOf success()
Named constructors.
Definition result.hpp:238
::rclcpp::ServiceHandle< S > SharedPtr
Definition service.hpp:130
nros_cpp_shutdown_callback_handle_t value() const
The raw FFI value, for passing to the C entry points directly.
Definition executor.hpp:67
::rclcpp::SubscriptionHandle< M > SharedPtr
Definition subscription.hpp:219
Definition timer.hpp:115
rclcpp::Future<T> — single-shot deferred result.
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)
TimerHandle register_timer_capturing(::rclcpp::Node &node, rcl_clock_type_t clock_type, Duration period, Cb &&cb, const char *verb)
Definition rclcpp.hpp:1084
void abort_failed_create(const char *verb, const char *name, int32_t code)
Definition rclcpp.hpp:337
void require_created(::rclcpp::Result r, const char *verb, const char *name)
Definition rclcpp.hpp:352
constexpr bool is_ros_args_flag(char const *s, int i=0)
Definition rclcpp.hpp:284
inline ::rclcpp::Node & as_node_ref(const ::rclcpp::Node::SharedPtr &n)
Definition rclcpp.hpp:1137
constexpr bool argv_has_ros_args(int argc, char const *const *argv, int i=0)
Definition rclcpp.hpp:291
Definition rclcpp.hpp:75
void spin(const Node::SharedPtr &node)
Definition rclcpp.hpp:854
inline ::rclcpp::TimerHandle create_timer(NodeT &&node, ::rclcpp::Clock *clock, ::rclcpp::Duration period, CallbackT &&callback)
Definition rclcpp.hpp:1156
void spin_some(const Node::SharedPtr &node)
Definition rclcpp.hpp:861
FutureReturnCode spin_until_future_complete(const Node::SharedPtr &node, const Future &future, int32_t timeout_ms=-1)
Definition rclcpp.hpp:886
Result init_in(const char *locator=nullptr, uint8_t domain_id=0)
Definition node.hpp:2785
ResultOf< void > Result
Definition result.hpp:246
Result spin_once(int32_t timeout_ms=10)
Definition rclcpp.hpp:97
FutureReturnCode
Definition rclcpp.hpp:447
@ TIMEOUT
The deadline passed with the future still pending.
@ INTERRUPTED
rclcpp::ok() went false (shutdown) before the future was ready.
void init()
rclcpp::init() — the zero-argument form. ADOPT.
Definition rclcpp.hpp:400
bool remove_on_shutdown_callback(OnShutdownCallbackHandle handle)
Definition rclcpp.hpp:178
bool ok()
Definition node.hpp:3031
Result spin_in()
Definition rclcpp.hpp:192
void * global_handle()
Definition node.hpp:2954
Result on_shutdown(ShutdownCallback callback, void *context=nullptr, OnShutdownCallbackHandle *out=nullptr)
Definition rclcpp.hpp:156
bool shutdown()
Definition rclcpp.hpp:417
bool remove_pre_shutdown_callback(PreShutdownCallbackHandle handle)
Definition rclcpp.hpp:170
void arm_monitors()
Definition rclcpp.hpp:113
Result pre_shutdown(ShutdownCallback callback, void *context=nullptr, PreShutdownCallbackHandle *out=nullptr)
Definition rclcpp.hpp:137
rclcpp::Node and global session helpers.
rclcpp::Publisher<M> — typed topic publisher.
rclcpp::QoS — full DDS-shaped QoS settings (Phase 108.B.7).
rclcpp::Client<S> — the arena-registered (callback-style) service client, and Client<S>::SharedPtr = ...
rclcpp::Result, rclcpp::ErrorCode, and the RCLCPP_TRY macro.
rclcpp::format_of<M> — a message type's serialization format; rclcpp::linked_format() — the linked ba...
rclcpp::Service<S> — the arena-registered (callback-style) service server, and Service<S>::SharedPtr ...
rclcpp::Stream<T> — multi-shot message receiver.
rclcpp::Subscription<M> — the typed DISPATCH subscriber: the arena owns it and the executor calls you...