pub struct EmbeddedPublisher<M> { /* private fields */ }Expand description
Typed publisher handle.
Two methods, both byte-oriented at the wire:
publish/publish_with_buffer— accept&M: RosMessage, CDR-encode into a stack buffer, then callPublisher::publish_raw.publish_raw— accepts pre-encoded CDR bytes for callers that already produced the wire payload.
No typed loan() exists. Loan/borrow live exclusively on
EmbeddedRawPublisher / RawSubscription. try_loan(len)
requires the byte length up front, which CDR ser/de can only
discover after encoding — the two APIs are incompatible by
construction. See docs/design/0010-zero-copy-raw-api.md decision D7.
Implementations§
Source§impl<M> EmbeddedPublisher<M>where
M: RosMessage,
impl<M> EmbeddedPublisher<M>where
M: RosMessage,
Sourcepub fn publish(&self, msg: &M) -> Result<(), NodeError>
pub fn publish(&self, msg: &M) -> Result<(), NodeError>
Publish a message using the default buffer size.
Sourcepub fn publish_with_buffer<const BUF: usize>(
&self,
msg: &M,
) -> Result<(), NodeError>
pub fn publish_with_buffer<const BUF: usize>( &self, msg: &M, ) -> Result<(), NodeError>
Publish a message with a custom buffer size.
Sourcepub fn publish_raw(&self, data: &[u8]) -> Result<(), NodeError>
pub fn publish_raw(&self, data: &[u8]) -> Result<(), NodeError>
Publish raw CDR-encoded data (must include CDR header).
Sourcepub fn publish_streamed<F>(
&self,
total_len: usize,
writer: F,
) -> Result<(), NodeError>
pub fn publish_streamed<F>( &self, total_len: usize, writer: F, ) -> Result<(), NodeError>
Phase 124.E.1 — streamed publish. Use a closure-driven writer to serialise straight into the backend’s outbound buffer without a per-publisher staging copy. Saves on RAM-constrained nodes that publish multi-KB payloads.
The writer closure receives a StreamWriter mutable
reference and uses StreamWriter::write / extend /
reserved_len to fill the slot in chunks. The total
payload length must be declared up-front via
StreamWriter::reserve_total; the backend allocates that
many bytes in its outbound buffer before any chunks land.
Backends without a native stream slot fall through to a
stack-allocated staging buffer (capped at 4 KiB) + a single
publish_raw — same observable result, just no zero-staging
win for the big-message case.
Sourcepub fn assert_liveliness(&self) -> Result<(), NodeError>
pub fn assert_liveliness(&self) -> Result<(), NodeError>
Phase 108.B — manually assert this publisher’s liveliness.
Required for publishers configured with
QoSLivelinessPolicy::ManualByTopic /
QoSLivelinessPolicy::ManualByNode. No-op for AUTOMATIC /
NONE kinds. Returns Err(Unsupported) if the backend doesn’t
implement manual liveliness — which, since the phase-467 RMW
gap-closure design study’s Row 7, includes zenoh under a manual
kind: its liveliness is a session keepalive, and a Ok(()) from a
call that put nothing on the wire was the wrong answer. Cyclone
performs the assertion for real.
Sourcepub fn get_gid(&self) -> Result<[u8; 24], NodeError>
pub fn get_gid(&self) -> Result<[u8; 24], NodeError>
This publisher’s own global identifier — upstream
rmw_get_gid_for_publisher / rclcpp::PublisherBase::get_gid.
The phase-467 RMW gap-closure design study’s Q1. 24 bytes,
nros_rmw::PUBLISHER_GID_SIZE, the width upstream’s
RMW_GID_STORAGE_SIZE uses.
Three bounds, each of which a caller will otherwise assume away:
- How many of the 24 bytes mean anything is a BACKEND property.
zenoh fills 16 and zero-extends the rest; Cyclone fills a 16-byte
DDS writer GUID the same way. The padding has one spelling
(
nros_rmw::pad_publisher_gid) so two gids naming one entity compare equal. - Comparable with a gid from a TAKE on zenoh and Cyclone. A
received sample’s
MessageInfo::publisher_gidis produced from the same source as this (issue 1495): zenoh’s XXH3-128 of the publisher’s liveliness keyexpr, Cyclone’s writer GUID — the same gidros2 topic info --verboseprints. Err(Unsupported)is a real answer, and it is not zero. A backend with no identity for this publisher (XRCE, uORB) says so; nothing here ever hands back an all-zero gid, because that is what an unwritten buffer holds.
Sourcepub fn supports_event(&self, kind: EventKind) -> bool
pub fn supports_event(&self, kind: EventKind) -> bool
true if the active backend can fire the named event for this
publisher.
Sourcepub fn on_liveliness_lost<F>(&mut self, cb: F) -> Result<(), NodeError>
pub fn on_liveliness_lost<F>(&mut self, cb: F) -> Result<(), NodeError>
Register a callback for LivelinessLost. Fires when this
publisher misses its own liveliness assertion deadline.
Sourcepub fn on_offered_deadline_missed<F>(
&mut self,
deadline: Duration,
cb: F,
) -> Result<(), NodeError>
pub fn on_offered_deadline_missed<F>( &mut self, deadline: Duration, cb: F, ) -> Result<(), NodeError>
Register a callback for OfferedDeadlineMissed. Fires when
this publisher promised deadline and falls behind.
Sourcepub fn topic_name(&self) -> &str
pub fn topic_name(&self) -> &str
The topic this publisher was created on — rclrs’s
PublisherState::topic_name, C’s rcl_publisher_get_topic_name,
C++’s Publisher::get_topic_name. phase-444.
&self, which is upstream’s own signature. The ledger row this
closes (rust:Publisher::topic_name) argued the accessor had to take
the node or the executor as well, because “our Rust entity handle is a
bare arena index with no back-reference — PublisherHandle { index, _marker }”. Measured, that is a DIFFERENT type:
Node::create_publisher
hands back an EmbeddedPublisher, which OWNS its backend handle, and
that handle has retained the name since creation
(CffiPublisher::topic_name, written from the caller’s string at
publisher_create). Nothing is indexed and nothing is re-resolved.
BORROWED, never owned: rclrs returns String, which needs an allocator
a 128 KiB target does not have. The bytes live in the backend handle for
as long as the publisher does, so a caller that wants an owned copy can
make one and a caller that does not pays nothing.
Not on the nros_rmw::Publisher trait, deliberately. The C-ABI
shim above every backend already retains the name, so a trait method
would make each backend keep a SECOND copy of a 256-byte string for a
call that never reaches it. Contrast actual_qos, which IS on the trait
because only the backend can answer it.
Sourcepub fn get_subscription_count(
&self,
executor: &mut Executor<'_>,
) -> Result<usize, NodeError>
pub fn get_subscription_count( &self, executor: &mut Executor<'_>, ) -> Result<usize, NodeError>
How many subscriptions this publisher reaches, RIGHT NOW —
rclcpp’s Publisher::get_subscription_count, rcl’s
rclc_publisher_get_subscription_count. phase-444, issue 1643.
Upstream’s question where the backend can answer it, a weaker one
where it cannot — and the difference belongs here rather than in a
surprise. Upstream counts the subscriptions MATCHED to THIS
publisher — peers whose QoS is compatible with it — through
rmw_publisher_count_matched_subscriptions. A backend that fills that
slot (Cyclone) is asked first and its answer is upstream’s exactly.
Only when it answers Unsupported does this fall back to the graph:
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.
The fallback is 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, which upstream does not, and that is forced: we
open ONE transport session per image and the Executor owns it, so
the graph is the executor’s to read — the same receiver difference as
every other graph verb (phase-381 W4). The compiler demands the
argument, 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”; and
Err(Transport(Unsupported)) from a backend with no graph is a
DIFFERENT answer from 0, which must not be collapsed into one.