|
nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
|
#include <publisher.hpp>
Classes | |
| class | Loan |
Public Types | |
| using | ConstSharedPtr = ::rclcpp::Owned< Publisher< M > > |
| using | SharedPtr = ::rclcpp::Owned< Publisher< M > > |
| using | UniquePtr = ::rclcpp::Owned< Publisher< M > > |
Static Public Attributes | |
| static constexpr size_t | GID_SIZE = 24 |
Friends | |
| class | ::rclcpp::Node |
Typed publisher for a ROS 2 topic.
Mirrors upstream's rclcpp::Publisher<M>. The message type M must provide TYPE_NAME, TYPE_HASH, and ffi_publish() (generated by codegen).
Raw CDR publishing is always available via publish_raw().
Inline storage holds the runtime publisher handle directly (NROS_PUBLISHER_SIZE bytes; size auto-derived from the runtime) plus one pointer: the contracted endpoint's monitor cell (phase-462 W1, RFC-0052), which the runtime resolves at create time against the table the entry installed with nros_cpp_install_monitors and bumps on every publish, exactly as the Rust EmbeddedPublisher does. Null on an uncontracted publisher. Topic name metadata lives C++-side in topic_name_, avoiding a runtime hop for get_topic_name().
WHY THIS ONE IS NOT AN ARENA HANDLE — phase-456 W4
Every other entity in this API is moving to a two-word handle over the Rust executor arena, because the arena already owns the entity and the C++ object was a second copy of state. A publisher is the exception, decided rather than left over.
The arena holds only things the executor DISPATCHES to — its EntryKind is subscription, service, client, timer, action server, action client, guard condition — and a publisher is none of them. The Rust API says the same by construction: Node::create_publisher_with_qos hands back an EmbeddedPublisher<M> BY VALUE, a caller-owned value with a Drop. So the phase's principle, that entity lifetime is a Rust data structure's, is already satisfied here, and rclcpp::Owned<Publisher<M>> mirrors that structure exactly.
It was also argued that it could not be moved without losing something — the arena then had no removal path, so an arena publisher could never have been destroyed before its executor. 9768795b1d (issue 1496) retired that argument; the one above, that an arena slot would make C++ diverge from the Rust lifetime, is what carries the decision.
Measured, so the question does not get re-opened on a size argument: sizeof(Publisher<M>) is 872 bytes and INDEPENDENT of M — identical for a 512-byte-bound string, an 8-byte Int32 and a 65 552-byte image. It is storage_ 608 + topic_name_ 256 + initialized_ + padding. Nothing here is a transmit buffer sized from the type's bound: serialisation goes into the backend's outbound buffer, and publish_streamed stages on the stack. That is what separates this type from the 888-byte Subscription<M> whose storage_ went unused on the dispatch path, and from the 4 672-byte Client<int> that carried a reply buffer.
Usage:
| using rclcpp::Publisher< M >::ConstSharedPtr = ::rclcpp::Owned<Publisher<M> > |
rclcpp::Publisher<M>::ConstSharedPtr — the SAME type as SharedPtr.
Owned<const T> is refused by a static_assert in owned.hpp: it declares cleanly and is ill-formed on the first move or reset(), which is worse than a refusal. A const/mutable distinction drawn over a handle presupposes shared ownership, which a sole owner does not have; const SharedPtr& already yields the const Publisher<M>* view.
| using rclcpp::Publisher< M >::SharedPtr = ::rclcpp::Owned<Publisher<M> > |
rclcpp::Publisher<M>::SharedPtr — phase-456 W5.
rclcpp::Publisher<M>::SharedPtr member_; is close to universal in ported source, so this alias must exist on EVERY target — which std::shared_ptr does not. It names rclcpp::Owned<Publisher<M>>: the publisher BY VALUE, with operator-> so every ported pub_->publish(m) keeps working, and no allocator, control block or <memory> anywhere.
IT IS NOT AN ARENA HANDLE, and that is a decision rather than an omission — W4 measured the case and refused it. It was first argued from the arena having no removal path (EntryKind has no publisher at all), so that ~Publisher() and Owned<T>::reset() would become no-ops; that argument expired with 9768795b1d (issue 1496). The Rust side is what settles it: create_publisher_with_qos returns an EmbeddedPublisher<M> BY VALUE, with a Drop, and this phase's principle is that the C++ lifetime mirrors the Rust one. owned.hpp carries the long form.
WHAT CHANGED FOR A PORTED FILE: one reference, not a shared count. The entity is destroyed when the holder is, which is what upstream does when the holder was the last owner — and the W0 census found no entity handle copied anywhere in this tree or the porting corpus.
| using rclcpp::Publisher< M >::UniquePtr = ::rclcpp::Owned<Publisher<M> > |
rclcpp::Publisher<M>::UniquePtr — also the SAME type as SharedPtr.
Owned<T> IS unique ownership — move-only, one owner, destroys on scope exit — so a separate alias would be a second spelling of one type. Measured before the collapse (phase-456 W5): zero uses in the tree outside this definition and the one probe asserting its old shape.
|
inline |
Destructor — releases publisher resources.
|
inline |
Default constructor — creates an uninitialized publisher. Use Node::create_publisher() to initialize.
|
inline |
Phase 108.B.7 — manually assert liveliness. Required for publishers configured with LivelinessManualByTopic / ManualByNode; nothing to assert for Automatic / None, which answer Ok on every backend.
**ErrorCode::Unsupported under a manual kind on a backend with no per-topic lease** — zenoh-pico, XRCE-DDS, uORB (the phase-467 RMW gap-closure design study's Row 7). The entity is fine; the assertion simply did not reach a peer, which is the one thing a caller uses this for. cyclonedds performs it for real.
|
inline |
The QoS profile this publisher is ACTUALLY running — issue 1437.
rclcpp::Publisher::get_actual_qos: what the BACKEND GRANTED, which differs from what create_publisher was handed whenever the backend serves something else — zenoh clamps history depth to its receive ring, DDS negotiates, and a launch-lowered QoS override has already been folded in before either. That difference is what answers "why is
nothing arriving": a RELIABLE reader does not match a BEST_EFFORT writer, and a clamped depth drops samples the caller believed kept.
Per policy, and a policy the backend cannot report is an ABSENCE — ReliabilityPolicy::Unknown, DurabilityPolicy::Unknown, ... — not your request echoed back. A backend with no read-back at all (XRCE) answers Unknown in every field. So compare field by field and check for the sentinel first; agreement you did not check for may be silence.
Free: read once at create and retained on the handle, so this never re-enters the transport and is safe from a callback. An uninitialized publisher answers the all-absent profile.
|
inline |
This publisher's own global identifier — rclcpp::PublisherBase::get_gid.
The phase-467 RMW gap-closure design study's Q1. Fills out_gid with [GID_SIZE](GID_SIZE) bytes on Ok, and writes nothing otherwise.
Upstream returns const rmw_gid_t & and cannot fail. Ours can — a backend may have no identity for this publisher — and RFC-0018 leaves no exceptions to say so with, so the value leaves through a reference to a caller-placed array and the verdict through the Result. An array REFERENCE rather than a pointer is what makes the width a compile-time fact at every call site.
Three bounds, each of which a caller will otherwise assume away:
MessageInfo names this publisher by these bytes.ErrorCode::Unsupported, because all-zero is what an unwritten buffer holds and the two must not read alike.
|
inline |
How many subscriptions are on this publisher's topic, RIGHT NOW — rclcpp's Publisher::get_subscription_count. phase-444.
A WEAKER QUESTION THAN UPSTREAM'S, and it is stated here rather than discovered. Upstream counts the subscriptions MATCHED to THIS publisher — peers whose QoS is compatible with it. This counts every subscription DISCOVERED on the topic, matched or not, because that is what the graph can answer and a backend with no QoS negotiation cannot tell the two apart at all.
Non-inverting, and in the safe direction: a topic nobody subscribes to still reads 0, so the use this exists for — skip the work when nobody is listening — is answered exactly. An INCOMPATIBLE peer makes it read 1 where upstream reads 0, i.e. you do work nobody receives. Never the reverse.
Takes the executor and an out-parameter, neither of which upstream does. The executor because we open ONE transport session per image and it owns the session, so the graph is the executor's to read — the same receiver difference as every Executor::get_* graph verb (phase-381 W4). The out-parameter because RFC-0018 forbids exceptions, so the error has to be the return value. Both weakenings are ones the compiler makes a porter notice, which is RFC-0089's mechanical-edit test.
Reports what has been DISCOVERED and never blocks, so a small answer right after startup is "not seen yet", never "not there"; a backend that cannot read the graph returns an error, which is a DIFFERENT answer from 0 and leaves *out_count untouched.
Get the topic name.
|
inline |
Check if the publisher is initialized and valid.
|
inline |
|
inline |
Register a callback for liveliness-lost events on this publisher.
|
inline |
Register a callback for offered-deadline-missed events on this publisher.
|
inline |
Publish a typed message.
Calls the codegen-generated M::ffi_publish() which serializes the message to CDR and publishes it.
phase-417 stage 3 — the initialized_ guard. publish was the ONLY entry point on this class without one, and the one that matters most: M::ffi_publish reaches nros_cpp_publish_raw, whose only check is storage.is_null(), and storage_ is an in-object array that is never null. So an uninitialised publisher reinterpreted zero-initialised bytes as an RmwPublisher — silently wrong, then UB, with no diagnostic at any warning level. That is reachable straight from create_publisher, which returns a Result a ported node may not read and leaves out untouched on failure.
Returns Result, where upstream returns void and throws: no exceptions here (RFC-0018). Result is [[nodiscard]], so a ported pub->publish(msg); warns; an uninitialised publisher answers NotInitialized and sends nothing.
Publish raw CDR bytes.
|
inline |
Phase 124.E.1 — streamed publish.
total_len declares the full payload size; writer is invoked one or more times with a writable chunk and must return the bytes written. The runtime/backend allocates total_len bytes (in the outbound buffer when the backend natively streams, otherwise in a stack staging buffer capped at ~4 KiB) and drains the closure until full.
writer signature: size_t(uint8_t* chunk, size_t cap).
|
friend |
Width of the buffer get_gid() fills — upstream's RMW_GID_STORAGE_SIZE, the phase-467 RMW gap-closure design study's Q1(a). Named here so a call site spells the size once and the compiler checks it, rather than copying a 24 that has already moved once.