nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
subscription.hpp
Go to the documentation of this file.
1// nros-cpp: Subscription class
2// Freestanding C++ — no exceptions, no STL required
3
14#ifndef NROS_RCLCPP_SUBSCRIPTION_HPP
15#define NROS_RCLCPP_SUBSCRIPTION_HPP
16
17#include <cstdint>
18#include <cstddef>
19#include <string.h> // memcpy — `<cstring>` isn't in Zephyr's minimal libcpp
20
21#include "rclcpp/detail/traits.hpp"
23#include "rclcpp/detail/entity_name.hpp" // phase-444 — the one entity-name copy
24#include "rclcpp/executor.hpp" // phase-444 — the graph counter these forward to
25#include "rclcpp/result.hpp"
26#include "rclcpp/subscription_handle.hpp"
27#include "rclcpp/inplace_fn.hpp"
28#include "rclcpp/size_bound.hpp" // rclcpp::rx_buffer_capacity<M> — the receive-buffer size
29// RFC-0088 D5 — NROS_CPP_ASSERT_MESSAGE_FORMAT, expanded in the creators below.
31#include "rclcpp/stream.hpp"
32
33// phase-417 W1.a — `<memory>` for the nested pointer aliases below.
34// `NROS_CPP_HAS_SHARED_PTR` and the other five capability macros have ONE
35// definition site, and the measured reason the predicate needs both probes
36// (issues 0112, 1187, 1240) is stated there.
37#include "rclcpp/detail/std_detect.hpp"
38
39#include "rclcpp/detail/nros_cpp_ffi.h"
40
41// issue 1437 — `get_actual_qos()` returns a `rclcpp::QoS` BY VALUE from an
42// inline body, so the complete type must be here, not only by the time
43// `rclcpp/node.hpp` is pulled in below.
44//
45// AFTER `nros_cpp_ffi.h`, never before: `qos.hpp` defines the four
46// `nros_cpp_qos_*_t` enums ITSELF under `#ifndef NROS_CPP_FFI_H`, so
47// reaching it first makes the cbindgen header a REDEFINITION of all four.
48#include "rclcpp/qos.hpp"
49
50// Phase 189.M3.x — `nros_cpp_subscription_register` is excluded from cbindgen
51// (its Rust signature uses `RawSubscriptionCallback`, an external-crate type
52// alias cbindgen names without defining). Declare it locally with a plain
53// function-pointer typedef matching the ABI (`void(data, len, ctx)`), mirroring
54// the service.hpp callback-register treatment.
55extern "C" {
56typedef void (*nros_cpp_subscription_message_callback_t)(const uint8_t* data, size_t len,
57 void* ctx);
58
59// phase-402: the trailing `sched_context` / `callback_group` arguments moved
60// into `nros_cpp_subscription_options_t` (defined by the cbindgen output above);
61// a NULL `options` is all-defaults, i.e. the pre-phase-402 behaviour.
62nros_cpp_ret_t nros_cpp_subscription_register(const nros_cpp_node_t* node, const char* topic,
63 const char* type_name, const char* type_hash,
66 void* context, size_t* out_handle_id,
67 const nros_cpp_subscription_options_t* options);
68
92 const nros_cpp_node_t* node, const char* topic, const char* type_name, const char* type_hash,
93 nros_cpp_qos_t qos, nros_cpp_subscription_message_callback_t callback, const uint8_t* capture,
94 size_t capture_len, void (*capture_drop)(void* capture), size_t* out_handle_id,
95 const nros_cpp_subscription_options_t* options);
96
97// Phase 189.M3.4 — callback-style register that also delivers the sample's wire
98// attachment (5-arg trampoline). Same cbindgen-exclusion reason as above.
99typedef void (*nros_cpp_subscription_message_info_callback_t)(const uint8_t* data, size_t len,
100 const uint8_t* attachment,
101 size_t attachment_len, void* ctx);
102
104 const nros_cpp_node_t* node, const char* topic, const char* type_name, const char* type_hash,
106 size_t* out_handle_id, const nros_cpp_subscription_options_t* options);
107// Phase 269 W3 — callback-style subscription that delivers the sample's E2E
108// integrity status alongside the CDR bytes. Same cbindgen-exclusion reason as
109// above (takes `RawSubscriptionSafetyCallback`, an external nros-node alias
110// gated on the `safety-e2e` feature). The 6-arg trampoline unpacks the three
111// integrity scalars; `subscription.hpp` repacks them into
112// `nros_cpp_integrity_status_t` for the typed user handler. Gated on the
113// `NROS_SAFETY_E2E` build feature (lowered from `[system].features =
114// ["safety"]` via `NanoRosCapabilities.cmake`).
115#if defined(NROS_SAFETY_E2E)
116typedef void (*nros_cpp_subscription_validated_callback_t)(const uint8_t* data, size_t len,
117 int64_t gap, bool duplicate,
118 int8_t crc_valid, void* ctx);
119
120nros_cpp_ret_t nros_cpp_subscription_register_validated(
121 const nros_cpp_node_t* node, const char* topic, const char* type_name, const char* type_hash,
122 nros_cpp_qos_t qos, nros_cpp_subscription_validated_callback_t callback, void* context,
123 size_t* out_handle_id, const nros_cpp_subscription_options_t* options);
124#endif // NROS_SAFETY_E2E
125} // extern "C"
126
127namespace rclcpp {
128
134using IntegrityStatus = ::nros_cpp_integrity_status_t;
135
142static constexpr size_t SUBSCRIPTION_TOPIC_NAME_MAX = 256;
143
144} // namespace rclcpp
145
153namespace rclcpp {
154class Node;
155}
156
157// ============================================================================
158// `rclcpp::Subscription<M>` -- DEFINED here (RFC-0089: rclcpp:: is the home)
159// ============================================================================
160//
161// phase-428: the definition moved from `nros::` to `rclcpp::` (the `nros::`
162// alias went in phase-483 W1). The nested `SharedPtr` / `ConstSharedPtr` / `UniquePtr`
163// aliases live on the class itself, so `rclcpp::Subscription<M>::SharedPtr`
164// resolves with no wrapper type in between.
165namespace rclcpp {
166
196template <typename M> class Subscription {
197 public:
226
230 using TypedSubscriptionFn = void (*)(const M& msg);
231 using TypedSubscriptionFnWithCtx = void (*)(const M& msg, void* ctx);
232 // Phase 189.M3.4 — callback-with-attachment handler (`bridge_origin` etc.).
234 size_t attachment_len);
235 // Phase 269 W3 — callback-with-integrity handler: receives the deserialized
236 // message plus the sample's E2E CRC/sequence status. Requires
237 // `NROS_SAFETY_E2E` (lowered from `[system].features = ["safety"]`).
238#if defined(NROS_SAFETY_E2E)
239 using TypedSubscriptionSafetyFn = void (*)(const M& msg,
240 const ::rclcpp::IntegrityStatus& integrity);
241#endif // NROS_SAFETY_E2E
242
244 const char* get_topic_name() const { return initialized_ ? topic_name_ : ""; }
245
247 bool is_valid() const { return initialized_; }
248
261 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
262 return executor.count_publishers(topic_name_, out_count);
263 }
264
274 ~Subscription() { initialized_ = false; }
275
276 // Move semantics (non-copyable). Bookkeeping only — there is no storage to
277 // relocate, so `nros_cpp_subscription_relocate` is not called here.
278 //
279 // THE HAZARD, narrowed to its real subject: the out-ref creators below hand
280 // the arena `&out` as the trampoline context, so an object registered that
281 // way must NOT be moved afterwards — the move copies the handler pointers
282 // and leaves the arena dispatching into the source object's address. The
283 // returning `create_subscription` (`nros.hpp`) has no such hazard: W1 put
284 // the capture in the arena and it hands back a `SharedPtr`, which is a
285 // value with nothing pointing at it.
286 Subscription(Subscription&& other) : initialized_(other.initialized_) {
287 ::memcpy(topic_name_, other.topic_name_, sizeof(topic_name_));
288 user_fn_ = other.user_fn_;
289 user_fn_ctx_ = other.user_fn_ctx_;
290 user_ctx_ = other.user_ctx_;
291 user_fn_info_ = other.user_fn_info_;
292#if defined(NROS_SAFETY_E2E)
293 user_fn_safety_ = other.user_fn_safety_;
294#endif // NROS_SAFETY_E2E
295 sched_handle_id_ = other.sched_handle_id_;
296 executor_ = other.executor_;
297 other.initialized_ = false;
298 }
299
301 if (this != &other) {
302 initialized_ = other.initialized_;
303 ::memcpy(topic_name_, other.topic_name_, sizeof(topic_name_));
304 user_fn_ = other.user_fn_;
305 user_fn_ctx_ = other.user_fn_ctx_;
306 user_ctx_ = other.user_ctx_;
307 user_fn_info_ = other.user_fn_info_;
308#if defined(NROS_SAFETY_E2E)
309 user_fn_safety_ = other.user_fn_safety_;
310#endif // NROS_SAFETY_E2E
311 sched_handle_id_ = other.sched_handle_id_;
312 executor_ = other.executor_;
313 other.initialized_ = false;
314 }
315 return *this;
316 }
317
320 Subscription() : topic_name_{}, initialized_(false) {}
321
332 bool has_sched_handle() const { return sched_handle_id_ != static_cast<size_t>(-1); }
333 size_t sched_handle_id() const { return sched_handle_id_; }
334
359 if (!initialized_ || executor_ == nullptr ||
360 nros_cpp_subscription_get_actual_qos(nullptr, executor_, sched_handle_id_, &f) != 0) {
361 return ::rclcpp::detail::qos_all_unknown();
362 }
363 return ::rclcpp::detail::qos_from_ffi(f);
364 }
365
366 private:
367 Subscription(const Subscription&) = delete;
368 Subscription& operator=(const Subscription&) = delete;
369
370 friend class ::rclcpp::Node;
371
375 static void message_trampoline(const uint8_t* data, size_t len, void* ctx) {
376 auto* self = static_cast<Subscription*>(ctx);
377 if (self == nullptr) return;
378 M msg;
379 if (M::ffi_deserialize(data, len, &msg) != 0) return;
380 if (self->user_fn_ != nullptr) {
381 self->user_fn_(msg);
382 } else if (self->user_fn_ctx_ != nullptr) {
383 self->user_fn_ctx_(msg, self->user_ctx_);
384 }
385 }
386
390 static void message_info_trampoline(const uint8_t* data, size_t len, const uint8_t* attachment,
391 size_t attachment_len, void* ctx) {
392 auto* self = static_cast<Subscription*>(ctx);
393 if (self == nullptr) return;
394 M msg;
395 if (M::ffi_deserialize(data, len, &msg) != 0) return;
396 if (self->user_fn_info_ != nullptr) {
397 self->user_fn_info_(msg, attachment, attachment_len);
398 }
399 }
400
401#if defined(NROS_SAFETY_E2E)
407 static void message_safety_trampoline(const uint8_t* data, size_t len, int64_t gap,
408 bool duplicate, int8_t crc_valid, void* ctx) {
409 auto* self = static_cast<Subscription*>(ctx);
410 if (self == nullptr) return;
411 M msg;
412 if (M::ffi_deserialize(data, len, &msg) != 0) return;
413 if (self->user_fn_safety_ != nullptr) {
414 nros_cpp_integrity_status_t status;
415 status.gap = gap;
416 status.duplicate = duplicate;
417 status.crc_valid = crc_valid;
418 self->user_fn_safety_(msg, status);
419 }
420 }
421#endif // NROS_SAFETY_E2E
422
429 void store_topic_name(const char* topic) {
430 size_t n = 0;
431 while (topic[n] != '\0' && n + 1 < sizeof(topic_name_)) {
432 topic_name_[n] = topic[n];
433 ++n;
434 }
435 topic_name_[n] = '\0';
436 }
437
438 // phase-456 W2b — `storage_` (NROS_SUBSCRIBER_SIZE bytes) and `stream_` are
439 // GONE, with the taking API that was their only reader. On this path the
440 // arena owns the subscriber, so those bytes were never filled, and
441 // `take()` reinterpreted them (`&mut *(storage as *mut RmwSubscriber)`)
442 // rather than refusing. `callback_mode_` went with them: it distinguished
443 // the two owners inside one class, and there is one owner here now.
445 bool initialized_;
446 // Phase 189.M3.1 — executor HandleId for sched-context binding, or
447 // SIZE_MAX (the default) when the registration has not happened yet.
448 size_t sched_handle_id_ = static_cast<size_t>(-1);
449 // The executor that owns the arena entry `sched_handle_id_` indexes —
450 // the phase-467 RMW gap-closure design study's Row 13.
451 //
452 // A `HandleId` is EXECUTOR-SCOPED: an index into one executor's arena, not
453 // a process-wide identity, and RFC-0002 puts one executor on one RTOS
454 // task, so a tiered image has several. That is why `get_actual_qos()`
455 // could not be answered from `sched_handle_id_` alone, and why resolving
456 // it "from the sched handle" would have needed a global executor
457 // registry. The pair is the same one `rclcpp::Client` already keeps
458 // (`{executor_, handle_id_}`), for the same reason.
459 void* executor_ = nullptr;
460 // The user's handler, dispatched by `message_trampoline` during spin.
461 TypedSubscriptionFn user_fn_ = nullptr;
462 TypedSubscriptionFnWithCtx user_fn_ctx_ = nullptr;
463 TypedSubscriptionInfoFn user_fn_info_ = nullptr;
464 void* user_ctx_ = nullptr;
465#if defined(NROS_SAFETY_E2E)
466 // Phase 269 W3 — handler for the integrity-carrying callback path; nullptr
467 // when not using `create_subscription_with_safety`.
468 TypedSubscriptionSafetyFn user_fn_safety_ = nullptr;
469#endif // NROS_SAFETY_E2E
470};
471
472} // namespace rclcpp
473
474// Phase 84.G8: out-of-line definition of Node::create_subscription<M>().
475#include "rclcpp/node.hpp"
476
477namespace rclcpp {
478
479// Phase 189.M3.x — callback-style (arena-registered) subscription. The arena
480// owns the subscriber + dispatches `out`'s message handler during spin_once, so
481// the handle is real and `options.sched_context` is functional. Mirrors the
482// callback-style `create_service` one entity over.
483} // namespace rclcpp
484
485namespace rclcpp {
486template <typename M, typename F, typename>
487Result Node::create_subscription(Subscription<M>& out, const char* topic, const ::rclcpp::QoS& qos,
488 F callback, const ::rclcpp::SubscriptionOptions& options) {
489 // RFC-0088 D5 — one image, one backend, one encoding. Compile-time, so a
490 // message the linked backend cannot encode never reaches the wire.
492 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
493 nros_cpp_qos_t ffi_qos = ::rclcpp::detail::qos_to_ffi(qos);
494
495 // Store the user handler (compile error if F isn't convertible to the
496 // plain-fn-ptr handler type).
497 out.user_fn_ = typename Subscription<M>::TypedSubscriptionFn(callback);
498 out.user_fn_ctx_ = nullptr;
499 out.user_ctx_ = nullptr;
500
501 uint8_t sched = (options.sched_context == ::rclcpp::SCHED_CONTEXT_UNSET)
502 ? 0u
503 : static_cast<uint8_t>(options.sched_context);
504 size_t handle = static_cast<size_t>(-1);
505 // phase-402: `sched_context` is a FIELD now. `callback_group` stays unset,
506 // i.e. the default group.
507 nros_cpp_subscription_options_t ffi_options = nros_cpp_subscription_default_options();
508 ffi_options.sched_context = sched;
509 // phase-456 W7 — state the bound. `M` is a template parameter of this
510 // function, so the number was always available here; leaving it at the
511 // option default put this registration on issue 1319's `c_raw_no_hint` row
512 // while the sizing descriptor credited the entry with a supplied hint.
513 ffi_options.rx_buffer_hint = static_cast<uint32_t>(::rclcpp::rx_buffer_capacity<M>::value);
515 &handle_, topic, M::TYPE_NAME, M::TYPE_HASH, ffi_qos, &Subscription<M>::message_trampoline,
516 &out, &handle, &ffi_options);
517 if (ret == 0) {
518 out.sched_handle_id_ = handle;
519 out.executor_ = executor_handle_;
520 // phase-456 W2b — `get_topic_name()` answered "" on every callback-style
521 // subscription until this line existed.
522 out.store_topic_name(topic);
523 out.initialized_ = true;
524 }
525 return Result(ret);
526}
527} // namespace rclcpp
528
529namespace rclcpp {
530
531// Phase 273 (RFC-0047) — callback-style subscription **in** a named callback group.
532// Mirrors create_subscription (callback-style) exactly but passes group.get_name()
533// as `callback_group` so the executor binds the slot via group_sched_table.
534} // namespace rclcpp
535
536namespace rclcpp {
537template <typename M, typename F, typename>
538Result Node::create_subscription_in_group(const ::rclcpp::CallbackGroup& group,
539 Subscription<M>& out, const char* topic,
540 const ::rclcpp::QoS& qos, F callback,
541 const ::rclcpp::SubscriptionOptions& options) {
542 // RFC-0088 D5 — one image, one backend, one encoding. Compile-time, so a
543 // message the linked backend cannot encode never reaches the wire.
545 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
546 nros_cpp_qos_t ffi_qos = ::rclcpp::detail::qos_to_ffi(qos);
547
548 out.user_fn_ = typename Subscription<M>::TypedSubscriptionFn(callback);
549 out.user_fn_ctx_ = nullptr;
550 out.user_ctx_ = nullptr;
551
552 uint8_t sched = (options.sched_context == ::rclcpp::SCHED_CONTEXT_UNSET)
553 ? 0u
554 : static_cast<uint8_t>(options.sched_context);
555 size_t handle = static_cast<size_t>(-1);
556 // phase-402: both the sched slot and the Phase 273 group name are FIELDS now.
557 nros_cpp_subscription_options_t ffi_options = nros_cpp_subscription_default_options();
558 ffi_options.sched_context = sched;
559 ffi_options.callback_group = group.get_name();
560 // phase-456 W7 — see the ungrouped form above. A grouped subscription costs
561 // the arena exactly what an ungrouped one does, so it states the same bound.
562 ffi_options.rx_buffer_hint = static_cast<uint32_t>(::rclcpp::rx_buffer_capacity<M>::value);
564 &handle_, topic, M::TYPE_NAME, M::TYPE_HASH, ffi_qos, &Subscription<M>::message_trampoline,
565 &out, &handle, &ffi_options);
566 if (ret == 0) {
567 out.sched_handle_id_ = handle;
568 out.executor_ = executor_handle_;
569 // phase-456 W2b — `get_topic_name()` answered "" on every callback-style
570 // subscription until this line existed.
571 out.store_topic_name(topic);
572 out.initialized_ = true;
573 }
574 return Result(ret);
575}
576} // namespace rclcpp
577
578namespace rclcpp {
579
580// Phase 189.M3.4 — callback-style subscription that delivers the wire attachment.
581// Mirrors the callback `create_subscription` one step over, but stores the
582// `(const M&, attachment, att_len)` handler + registers via the with-info arena
583// path so the trampoline receives the attachment.
584} // namespace rclcpp
585
586namespace rclcpp {
587template <typename M, typename F, typename>
588Result Node::create_subscription_with_info(Subscription<M>& out, const char* topic,
589 const ::rclcpp::QoS& qos, F callback,
590 const ::rclcpp::SubscriptionOptions& options) {
591 // RFC-0088 D5 — one image, one backend, one encoding. Compile-time, so a
592 // message the linked backend cannot encode never reaches the wire.
594 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
595 nros_cpp_qos_t ffi_qos = ::rclcpp::detail::qos_to_ffi(qos);
596
597 out.user_fn_info_ = typename Subscription<M>::TypedSubscriptionInfoFn(callback);
598 out.user_fn_ = nullptr;
599 out.user_fn_ctx_ = nullptr;
600 out.user_ctx_ = nullptr;
601
602 uint8_t sched = (options.sched_context == ::rclcpp::SCHED_CONTEXT_UNSET)
603 ? 0u
604 : static_cast<uint8_t>(options.sched_context);
605 size_t handle = static_cast<size_t>(-1);
606 // phase-402: `sched_context` is a FIELD now.
607 nros_cpp_subscription_options_t ffi_options = nros_cpp_subscription_default_options();
608 ffi_options.sched_context = sched;
609 // phase-456 W7 — the attachment rides beside the sample and does not change
610 // how many bytes the sample itself needs, so this is the same bound the
611 // plain callback form states.
612 ffi_options.rx_buffer_hint = static_cast<uint32_t>(::rclcpp::rx_buffer_capacity<M>::value);
614 &handle_, topic, M::TYPE_NAME, M::TYPE_HASH, ffi_qos,
615 &Subscription<M>::message_info_trampoline, &out, &handle, &ffi_options);
616 if (ret == 0) {
617 out.sched_handle_id_ = handle;
618 out.executor_ = executor_handle_;
619 // phase-456 W2b — `get_topic_name()` answered "" on every callback-style
620 // subscription until this line existed.
621 out.store_topic_name(topic);
622 out.initialized_ = true;
623 }
624 return Result(ret);
625}
626} // namespace rclcpp
627
628namespace rclcpp {
629
630#if defined(NROS_SAFETY_E2E)
631// Phase 269 W3 — out-of-line definition of Node::create_subscription_with_safety.
632// Mirrors `create_subscription_with_info` one overload over, but routes through
633// `nros_cpp_subscription_register_validated` so the arena dispatches the
634// `message_safety_trampoline` on each new sample.
635} // namespace rclcpp
636
637namespace rclcpp {
638template <typename M, typename F, typename>
639Result Node::create_subscription_with_safety(Subscription<M>& out, const char* topic,
640 const ::rclcpp::QoS& qos, F callback,
641 const ::rclcpp::SubscriptionOptions& options) {
642 // RFC-0088 D5 — one image, one backend, one encoding. Compile-time, so a
643 // message the linked backend cannot encode never reaches the wire.
645 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
646 nros_cpp_qos_t ffi_qos = ::rclcpp::detail::qos_to_ffi(qos);
647
648 out.user_fn_safety_ = typename Subscription<M>::TypedSubscriptionSafetyFn(callback);
649 out.user_fn_ = nullptr;
650 out.user_fn_ctx_ = nullptr;
651 out.user_fn_info_ = nullptr;
652 out.user_ctx_ = nullptr;
653
654 uint8_t sched = (options.sched_context == ::rclcpp::SCHED_CONTEXT_UNSET)
655 ? 0u
656 : static_cast<uint8_t>(options.sched_context);
657 size_t handle = static_cast<size_t>(-1);
658 // phase-402: `sched_context` is a FIELD now.
659 nros_cpp_subscription_options_t ffi_options = nros_cpp_subscription_default_options();
660 ffi_options.sched_context = sched;
661 // phase-456 W7 — the validated path deserializes the same bytes; the safety
662 // status is computed from them, not received alongside them.
663 ffi_options.rx_buffer_hint = static_cast<uint32_t>(::rclcpp::rx_buffer_capacity<M>::value);
664 nros_cpp_ret_t ret = nros_cpp_subscription_register_validated(
665 &handle_, topic, M::TYPE_NAME, M::TYPE_HASH, ffi_qos,
666 &Subscription<M>::message_safety_trampoline, &out, &handle, &ffi_options);
667 if (ret == 0) {
668 out.sched_handle_id_ = handle;
669 out.executor_ = executor_handle_;
670 // phase-456 W2b — `get_topic_name()` answered "" on every callback-style
671 // subscription until this line existed.
672 out.store_topic_name(topic);
673 out.initialized_ = true;
674 }
675 return Result(ret);
676}
677} // namespace rclcpp
678
679namespace rclcpp {
680#endif // NROS_SAFETY_E2E
681
682namespace detail {
683
695template <typename M, typename Fn>
696inline Result register_subscription_capturing(::rclcpp::Node& node, const char* topic,
697 const QoS& qos, const Fn& fn, size_t* out_handle_id) {
698 // phase-456 W8 — the per-entry capture budget this used to assert is GONE.
699 // It read `sizeof(Fn) <= NROS_CPP_CALLBACK_CAPACITY + 2 * sizeof(void*)` and
700 // named `CALLBACK_CAPTURE_BYTES`, an arena constant that no longer exists:
701 // the runtime allocates exactly `capture_len` bytes now, so there is no
702 // cross-language number for the two sides to agree about and nothing here
703 // to assert. `InplaceFn`'s own capacity `static_assert` is untouched and is
704 // still the knob a too-large CALLABLE hits (`inplace_fn.hpp`), which is a
705 // property of that type rather than of this registration.
706 const nros_cpp_node_t* h = node.ffi_handle();
707 if (h == nullptr) return Result(ErrorCode::NotInitialized);
708
709 nros_cpp_subscription_message_callback_t invoke = [](const uint8_t* data, size_t len,
710 void* ctx) {
711 // `ctx` is the ARENA's copy of `fn`, not ours.
712 const Fn* self = static_cast<const Fn*>(ctx);
713 M msg;
714 if (M::ffi_deserialize(data, len, &msg) != 0) return;
715 (*self)(msg);
716 };
717
718 // phase-476 W2 — the arena's copy is destroyed when the subscription is
719 // released. `Fn` is an `rclcpp::InplaceFn` (the one caller passes one), so
720 // `clear()` runs the stored callable's own destructor.
721 void (*destroy)(void*) = [](void* ctx) { static_cast<Fn*>(ctx)->clear(); };
722
723 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
724 nros_cpp_subscription_options_t opts = {};
725 opts.rx_buffer_hint = static_cast<uint32_t>(::rclcpp::rx_buffer_capacity<M>::value);
727 h, topic, M::TYPE_NAME, M::TYPE_HASH, ffi_qos, invoke,
728 reinterpret_cast<const uint8_t*>(&fn), sizeof(Fn), destroy, out_handle_id, &opts));
729}
730
731} // namespace detail
732
733} // namespace rclcpp
734
735// ============================================================================
736// WHAT LEFT THIS HEADER
737// ============================================================================
738//
739// phase-456 W2 deleted `rclcpp::detail::SubscriptionCallback<M>` — the heap
740// cell whose only job was to give a capturing lambda a stable address. W1 put
741// the capture in the arena entry, so the cell had nothing left to hold, and
742// `nros.hpp`'s returning `create_subscription` stopped allocating one.
743//
744// phase-456 W2b moved the TAKING API — `take`, `take_sized`, `take_serialized`,
745// `take_serialized_with_attachment`, `take_validated`, `take_validated_sized`,
746// `take_sequence`, `try_borrow` + `View`, `stream()` and the three QoS-event setters — to
747// `rclcpp::PollSubscription<M>` in `rclcpp/polling_subscription.hpp`, together with
748// the out-ref creator that fills it. Those calls all dereference `storage_`,
749// which only a caller-owned subscriber ever has.
750
751#endif // NROS_RCLCPP_SUBSCRIPTION_HPP
Definition client.hpp:129
Definition executor.hpp:116
Result count_publishers(const char *topic_name, size_t *out_count)
Definition executor.hpp:321
Definition node.hpp:703
Result create_subscription_in_group(const ::rclcpp::CallbackGroup &group, ::rclcpp::Subscription< M > &out, const char *topic, const ::rclcpp::QoS &qos, F callback, const ::rclcpp::SubscriptionOptions &options={})
const nros_cpp_node_t * ffi_handle() const
Definition node.hpp:1475
Result create_subscription_with_info(::rclcpp::Subscription< M > &out, const char *topic, const ::rclcpp::QoS &qos, F callback, const ::rclcpp::SubscriptionOptions &options={})
typename::rclcpp::Subscription< M >::SharedPtr create_subscription(const char *topic, const ::rclcpp::QoS &qos, Cb cb)
Definition qos.hpp:237
Definition result.hpp:216
Definition subscription.hpp:196
bool has_sched_handle() const
Definition subscription.hpp:332
Result get_publisher_count(::rclcpp::Executor &executor, size_t *out_count) const
Definition subscription.hpp:260
Subscription(Subscription &&other)
Definition subscription.hpp:286
Subscription & operator=(Subscription &&other)
Definition subscription.hpp:300
size_t sched_handle_id() const
Definition subscription.hpp:333
Subscription()
Definition subscription.hpp:320
~Subscription()
Definition subscription.hpp:274
void(*)(const M &msg, const uint8_t *attachment, size_t attachment_len) TypedSubscriptionInfoFn
Definition subscription.hpp:234
void(*)(const M &msg, void *ctx) TypedSubscriptionFnWithCtx
Definition subscription.hpp:231
const char * get_topic_name() const
Get the topic name.
Definition subscription.hpp:244
::rclcpp::QoS get_actual_qos() const
Definition subscription.hpp:357
void(*)(const M &msg) TypedSubscriptionFn
Definition subscription.hpp:230
bool is_valid() const
Check if the subscription is initialized and valid.
Definition subscription.hpp:247
Inline storage-size macros for opaque entity buffers.
rclcpp::Executor — drives transport I/O and dispatches callbacks.
int nros_cpp_ret_t
Definition future.hpp:21
Definition rclcpp.hpp:75
ResultOf< void > Result
Definition result.hpp:246
static constexpr size_t SUBSCRIPTION_TOPIC_NAME_MAX
Definition subscription.hpp:142
rclcpp::Node and global session helpers.
rclcpp::QoS — full DDS-shaped QoS settings (Phase 108.B.7).
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...
#define NROS_CPP_ASSERT_MESSAGE_FORMAT(M)
Definition serialization_format.hpp:97
rclcpp::Stream<T> — multi-shot message receiver.
Definition qos.hpp:81
nros_cpp_ret_t nros_cpp_subscription_register_capturing(const nros_cpp_node_t *node, const char *topic, const char *type_name, const char *type_hash, nros_cpp_qos_t qos, nros_cpp_subscription_message_callback_t callback, const uint8_t *capture, size_t capture_len, void(*capture_drop)(void *capture), size_t *out_handle_id, const nros_cpp_subscription_options_t *options)
nros_cpp_ret_t nros_cpp_subscription_register(const nros_cpp_node_t *node, const char *topic, const char *type_name, const char *type_hash, nros_cpp_qos_t qos, nros_cpp_subscription_message_callback_t callback, void *context, size_t *out_handle_id, const nros_cpp_subscription_options_t *options)
void(* nros_cpp_subscription_message_info_callback_t)(const uint8_t *data, size_t len, const uint8_t *attachment, size_t attachment_len, void *ctx)
Definition subscription.hpp:99
nros_cpp_ret_t nros_cpp_subscription_register_with_info(const nros_cpp_node_t *node, const char *topic, const char *type_name, const char *type_hash, nros_cpp_qos_t qos, nros_cpp_subscription_message_info_callback_t callback, void *context, size_t *out_handle_id, const nros_cpp_subscription_options_t *options)
void(* nros_cpp_subscription_message_callback_t)(const uint8_t *data, size_t len, void *ctx)
Definition subscription.hpp:56