cyclonedds/publisher.rs
1use crate::internal::ffi;
2use crate::internal::traits::AsFfi;
3use crate::{Participant, Result};
4
5/// A `Publisher` groups [`Writers`](crate::Writer) and controls their shared
6/// [`QoS`](crate::QoS). Writers created under a publisher inherit its
7/// [`QoS`](crate::QoS) where applicable.
8///
9/// Use [`Publisher::new`] for simple construction or [`Publisher::builder`] for
10/// [`QoS`](crate::QoS) and [`listener`](crate::listener::PublisherListener)
11/// configuration.
12///
13/// In most applications a publisher is created implicitly when constructing a
14/// [`Writer`](crate::Writer) directly. Use an explicit publisher when you need
15/// coordinated writes across multiple writers.
16#[derive(Debug)]
17pub struct Publisher<'domain, 'participant> {
18 pub(crate) inner: cyclonedds_sys::dds_entity_t,
19 phantom: std::marker::PhantomData<&'participant Participant<'domain>>,
20}
21
22/// Builder for [`Publisher`] (accessible via [`Publisher::builder`]).
23#[derive(Debug)]
24pub struct PublisherBuilder<'domain, 'participant, 'qos> {
25 participant: &'participant Participant<'domain>,
26 qos: Option<&'qos crate::QoS>,
27 listener: Option<crate::PublisherListener>,
28}
29
30impl<'d, 'p, 'q> PublisherBuilder<'d, 'p, 'q> {
31 /// Creates a new [`PublisherBuilder`] for the given [`Participant`].
32 ///
33 /// # Examples
34 ///
35 /// ```
36 /// use cyclonedds::builder::PublisherBuilder;
37 /// use cyclonedds::{Domain, Participant};
38 ///
39 /// let domain = Domain::default();
40 /// let participant = Participant::new(&domain)?;
41 /// let publisher_builder = PublisherBuilder::new(&participant);
42 /// # Ok::<_, cyclonedds::Error>(())
43 /// ```
44 #[must_use]
45 pub const fn new(participant: &'p Participant<'d>) -> Self {
46 Self {
47 participant,
48 qos: None,
49 listener: None,
50 }
51 }
52
53 /// Sets the [`QoS`](crate::QoS) for this publisher builder.
54 ///
55 /// # Examples
56 ///
57 /// ```
58 /// use cyclonedds::builder::PublisherBuilder;
59 /// use cyclonedds::qos::policy;
60 /// use cyclonedds::{Duration, QoS};
61 /// # use cyclonedds::{Domain, Participant};
62 /// # let domain = Domain::default();
63 /// # let participant = Participant::new(&domain)?;
64 ///
65 /// let qos = QoS::new().with_reliability(policy::Reliability::Reliable {
66 /// max_blocking_time: Duration::from_millis(100),
67 /// });
68 /// let publisher_builder = PublisherBuilder::new(&participant).with_qos(&qos);
69 /// # Ok::<_, cyclonedds::Error>(())
70 /// ```
71 #[must_use]
72 pub const fn with_qos(mut self, qos: &'q crate::QoS) -> Self {
73 self.qos = Some(qos);
74 self
75 }
76
77 /// Sets the [`Listener`](crate::Listener) on this publisher builder.
78 ///
79 /// # Examples
80 ///
81 /// ```
82 /// use cyclonedds::Listener;
83 /// use cyclonedds::builder::PublisherBuilder;
84 /// # use cyclonedds::{Domain, Participant};
85 /// # let domain = Domain::default();
86 /// # let participant = Participant::new(&domain)?;
87 ///
88 /// let publisher_builder = PublisherBuilder::new(&participant).with_listener(Listener::new());
89 /// # Ok::<_, cyclonedds::Error>(())
90 /// ```
91 #[must_use]
92 pub fn with_listener<L>(mut self, listener: L) -> Self
93 where
94 L: AsRef<crate::PublisherListener>,
95 {
96 self.listener = Some(*listener.as_ref());
97 self
98 }
99
100 /// Builds the [`Publisher`].
101 ///
102 /// # Errors
103 ///
104 /// Returns an [`Error`](crate::Error) if the publisher failed to create.
105 ///
106 /// # Examples
107 ///
108 /// ```
109 /// use cyclonedds::QoS;
110 /// use cyclonedds::builder::PublisherBuilder;
111 /// use cyclonedds::qos::policy;
112 /// # use cyclonedds::{Domain, Participant};
113 /// # let domain = Domain::default();
114 /// # let participant = Participant::new(&domain)?;
115 ///
116 /// let qos = QoS::new().with_durability(policy::Durability::TransientLocal);
117 /// let publisher = PublisherBuilder::new(&participant).with_qos(&qos).build()?;
118 /// # Ok::<_, cyclonedds::Error>(())
119 /// ```
120 pub fn build(self) -> Result<Publisher<'d, 'p>> {
121 // NOTE: using `and_then` to avoid ? branch on the listener for coverage
122 // since the C lib currently panics on OOM rather than returning null.
123 self.listener
124 .map(|listener| listener.as_ffi())
125 .transpose()
126 .and_then(|listener| {
127 Ok(Publisher {
128 inner: ffi::dds_create_publisher(
129 self.participant.inner,
130 self.qos.map(|qos| &qos.inner),
131 listener.as_ref(),
132 )?,
133 phantom: std::marker::PhantomData,
134 })
135 })
136 }
137}
138
139impl<'d, 'p> Publisher<'d, 'p> {
140 /// Creates a new `Publisher` under `participant` with default
141 /// [`QoS`](crate::QoS) and no
142 /// [`listener`](crate::listener::PublisherListener).
143 ///
144 /// # Errors
145 ///
146 /// Returns an [`Error`](crate::Error) if the publisher fails to create.
147 ///
148 /// # Examples
149 ///
150 /// ```
151 /// use cyclonedds::Publisher;
152 /// # use cyclonedds::{Domain, Participant};
153 /// # let domain = Domain::default();
154 /// # let participant = Participant::new(&domain)?;
155 ///
156 /// let publisher = Publisher::new(&participant)?;
157 /// Ok::<_, cyclonedds::Error>(())
158 /// ```
159 pub fn new(participant: &'p Participant<'d>) -> Result<Self> {
160 Self::builder(participant).build()
161 }
162
163 /// Returns a [`PublisherBuilder`](crate::builder::PublisherBuilder) for
164 /// constructing a publisher with custom [`QoS`](crate::QoS) or a
165 /// [`listener`](crate::listener::PublisherListener).
166 ///
167 /// # Examples
168 ///
169 /// ```
170 /// use cyclonedds::qos::policy::{Durability, Presentation};
171 /// use cyclonedds::{Publisher, QoS};
172 /// # use cyclonedds::{Domain, Participant};
173 /// # let domain = Domain::default();
174 /// # let participant = Participant::new(&domain)?;
175 ///
176 /// let qos = QoS::new().with_presentation(Presentation::Topic {
177 /// coherent_access: true,
178 /// ordered_access: true,
179 /// });
180 /// let publisher = Publisher::builder(&participant).with_qos(&qos).build()?;
181 /// Ok::<_, cyclonedds::Error>(())
182 /// ```
183 #[must_use]
184 pub const fn builder<'q>(participant: &'p Participant<'d>) -> PublisherBuilder<'d, 'p, 'q> {
185 PublisherBuilder::new(participant)
186 }
187
188 /// (WARN: unimplemented in C lib): Suspends publication on all writers
189 /// belonging to this publisher.
190 ///
191 /// <div class="warning">
192 ///
193 /// This function is currently not implemented by the underlying C library
194 /// and will thus always return an unsupported error.
195 ///
196 /// </div>
197 ///
198 /// While suspended, calls to [`Writer::write`](crate::Writer::write) may
199 /// be batched by the middleware. Call [`resume`](Publisher::resume) to
200 /// flush and resume normal publication. Suspend and resume are typically
201 /// used together to send a coherent set of updates.
202 ///
203 /// # Errors
204 ///
205 /// Returns an [`Error`](crate::Error) if publisher fails to suspend.
206 ///
207 /// # Examples
208 ///
209 /// ```no_run
210 /// use cyclonedds::{Topic, Writer};
211 /// # use cyclonedds::{Domain, Participant, Publisher};
212 /// # let domain = Domain::default();
213 /// # let participant = Participant::new(&domain)?;
214 /// # #[derive(
215 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
216 /// # )]
217 /// # struct Data {
218 /// # x: i32,
219 /// # y: i32,
220 /// # }
221 /// let topic = Topic::<Data>::new(&participant, "MyTopic")?;
222 ///
223 /// // Create the publisher.
224 /// let publisher = Publisher::new(&participant)?;
225 ///
226 /// // Create two Writers under the publisher.
227 /// let writer01 = Writer::builder(&topic).with_publisher(&publisher).build()?;
228 /// let writer02 = Writer::builder(&topic).with_publisher(&publisher).build()?;
229 ///
230 /// // Suspend all the writers.
231 /// publisher.suspend()?;
232 ///
233 /// writer01.write(&Data { x: 0, y: 1 })?;
234 /// writer02.write(&Data { x: 2, y: 3 })?;
235 ///
236 /// // Resume all the writers.
237 /// publisher.resume()?;
238 ///
239 /// Ok::<_, cyclonedds::Error>(())
240 /// ```
241 pub fn suspend(&self) -> Result<()> {
242 ffi::dds_suspend(self.inner)
243 }
244
245 /// (WARN: unimplemented in C lib): Resumes publication on all writers
246 /// belonging to this publisher.
247 ///
248 /// <div class="warning">
249 ///
250 /// This function is currently not implemented by the underlying C library
251 /// and will thus always return an unsupported error.
252 ///
253 /// </div>
254 ///
255 /// Flushes any writes that were batched during a
256 /// [`suspend`](Publisher::suspend) and resumes normal publication.
257 ///
258 /// # Errors
259 ///
260 /// Returns an [`Error`](crate::Error) if the publisher fails to resume.
261 ///
262 /// # Examples
263 ///
264 /// ```no_run
265 /// use cyclonedds::{Topic, Writer};
266 /// # use cyclonedds::{Domain, Participant, Publisher};
267 /// # let domain = Domain::default();
268 /// # let participant = Participant::new(&domain)?;
269 /// # #[derive(
270 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
271 /// # )]
272 /// # struct Data {
273 /// # x: i32,
274 /// # y: i32,
275 /// # }
276 /// let topic = Topic::<Data>::new(&participant, "MyTopic")?;
277 ///
278 /// // Create the publisher.
279 /// let publisher = Publisher::new(&participant)?;
280 ///
281 /// // Create two Writers under the publisher.
282 /// let writer01 = Writer::builder(&topic).with_publisher(&publisher).build()?;
283 /// let writer02 = Writer::builder(&topic).with_publisher(&publisher).build()?;
284 ///
285 /// // Suspend all the writers.
286 /// publisher.suspend()?;
287 ///
288 /// writer01.write(&Data { x: 0, y: 1 })?;
289 /// writer02.write(&Data { x: 2, y: 3 })?;
290 ///
291 /// // Resume all the writers.
292 /// publisher.resume()?;
293 ///
294 /// Ok::<_, cyclonedds::Error>(())
295 /// ```
296 pub fn resume(&self) -> Result<()> {
297 ffi::dds_resume(self.inner)
298 }
299
300 /// (WARN: unimplemented in C lib): Blocks until all samples written by
301 /// writers under this publisher have been acknowledged by all matched
302 /// reliable readers, or until `timeout` elapses.
303 ///
304 /// <div class="warning">
305 ///
306 /// This function is currently not implemented by the underlying C library
307 /// and will thus always return an unsupported error.
308 ///
309 /// </div>
310 ///
311 ///
312 /// # Errors
313 ///
314 /// Returns an [`Error`](crate::Error) if the timeout elapses before all
315 /// acknowledgements are received or if the publisher returns an error.
316 ///
317 /// # Examples
318 ///
319 /// ```no_run
320 /// use cyclonedds::Duration;
321 /// # use cyclonedds::{Domain, Participant, Publisher};
322 /// # let domain = Domain::default();
323 /// # let participant = Participant::new(&domain)?;
324 ///
325 /// let publisher = Publisher::new(&participant)?;
326 /// publisher.wait_for_acks(Duration::from_secs(1))?;
327 /// Ok::<_, cyclonedds::Error>(())
328 /// ```
329 pub fn wait_for_acks(&self, timeout: crate::Duration) -> Result<()> {
330 ffi::dds_wait_for_acks(self.inner, timeout.inner)
331 }
332
333 #[allow(unused)]
334 pub(crate) const fn from_existing(
335 inner: cyclonedds_sys::dds_entity_t,
336 ) -> std::mem::ManuallyDrop<Self> {
337 std::mem::ManuallyDrop::new(Self {
338 inner,
339 phantom: std::marker::PhantomData,
340 })
341 }
342
343 /// Sets the [`PublisherListener`](crate::PublisherListener) on this
344 /// publisher, replacing any previously set listener.
345 ///
346 /// # Errors
347 ///
348 /// Returns an [`Error`](crate::Error) if the publisher fails to set the
349 /// listener.
350 ///
351 /// # Examples
352 ///
353 /// ```
354 /// use cyclonedds::PublisherListener;
355 /// # use cyclonedds::{Domain, Participant, Publisher};
356 /// # let domain = Domain::default();
357 /// # let participant = Participant::new(&domain)?;
358 ///
359 /// let mut publisher = Publisher::new(&participant)?;
360 /// publisher.set_listener(PublisherListener::new())?;
361 /// # Ok::<_, cyclonedds::Error>(())
362 /// ```
363 pub fn set_listener<L>(&mut self, listener: L) -> Result<()>
364 where
365 L: AsRef<crate::PublisherListener>,
366 {
367 listener
368 .as_ref()
369 .as_ffi()
370 .and_then(|listener| ffi::dds_set_listener(self.inner, Some(listener.inner)))
371 }
372
373 /// Removes the listener from this publisher.
374 ///
375 /// # Errors
376 ///
377 /// Returns an [`Error`](crate::Error) if the publisher fails to unset the
378 /// listener.
379 ///
380 /// # Examples
381 ///
382 /// ```
383 /// # use cyclonedds::{Domain, Participant, Publisher};
384 /// # let domain = Domain::default();
385 /// # let participant = Participant::new(&domain)?;
386 /// let mut publisher = Publisher::new(&participant)?;
387 /// publisher.unset_listener()?;
388 /// # Ok::<_, cyclonedds::Error>(())
389 /// ```
390 pub fn unset_listener(&mut self) -> Result<()> {
391 ffi::dds_set_listener(self.inner, None)?;
392 Ok(())
393 }
394
395 /// Sets the [`PublisherListener`](crate::PublisherListener) on this
396 /// publisher, consuming and returning `self`.
397 ///
398 /// # Errors
399 ///
400 /// Returns an [`Error`](crate::Error) if the publisher fails to set the
401 /// listener.
402 ///
403 /// # Examples
404 ///
405 /// ```
406 /// use cyclonedds::PublisherListener;
407 /// # use cyclonedds::{Domain, Participant, Publisher};
408 /// # let domain = Domain::default();
409 /// # let participant = Participant::new(&domain)?;
410 ///
411 /// let publisher = Publisher::new(&participant)?.with_listener(PublisherListener::new())?;
412 /// # Ok::<_, cyclonedds::Error>(())
413 /// ```
414 pub fn with_listener<L>(mut self, listener: L) -> Result<Self>
415 where
416 L: AsRef<crate::PublisherListener>,
417 {
418 self.set_listener(listener).map(|()| self)
419 }
420}
421
422impl Drop for Publisher<'_, '_> {
423 fn drop(&mut self) {
424 let result = ffi::dds_delete(self.inner);
425 debug_assert!(
426 result.is_ok(),
427 "unable to delete {self:?}: failed with {result:?}"
428 );
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn test_publisher_create() {
438 let domain_id = crate::tests::domain::unique_id();
439 let domain = crate::Domain::new(domain_id).unwrap();
440 let qos = crate::QoS::new();
441 let participant = Participant::new(&domain).unwrap();
442 let _ = Publisher::new(&participant).unwrap();
443 let _ = Publisher::builder(&participant)
444 .with_qos(&qos)
445 .build()
446 .unwrap();
447 }
448
449 #[test]
450 fn test_publisher_create_with_invalid_participant() {
451 let domain_id = crate::tests::domain::unique_id();
452 let domain = crate::Domain::new(domain_id).unwrap();
453 let qos = crate::QoS::new();
454 let mut participant = Participant::new(&domain).unwrap();
455 let participant_id = participant.inner;
456 participant.inner = 0;
457 let result = Publisher::new(&participant).unwrap_err();
458 assert_eq!(result, crate::Error::BadParameter);
459 let result = Publisher::builder(&participant)
460 .with_qos(&qos)
461 .build()
462 .unwrap_err();
463 assert_eq!(result, crate::Error::BadParameter);
464 participant.inner = participant_id;
465 }
466
467 #[test]
468 fn test_publisher_from_existing_publisher() {
469 let domain_id = crate::tests::domain::unique_id();
470 let domain = crate::Domain::new(domain_id).unwrap();
471 let participant = crate::Participant::new(&domain).unwrap();
472 let publisher = Publisher::new(&participant).unwrap();
473
474 let new_publisher = Publisher::from_existing(publisher.inner);
475
476 assert_eq!(new_publisher.inner, publisher.inner);
477 }
478
479 #[test]
480 fn test_publisher_suspend_not_yet_supported_by_c_lib() {
481 let domain_id = crate::tests::domain::unique_id();
482 let domain = crate::Domain::new(domain_id).unwrap();
483 let participant = crate::Participant::new(&domain).unwrap();
484 let publisher = Publisher::new(&participant).unwrap();
485
486 let result = publisher.suspend();
487 assert_eq!(
488 result,
489 Err(crate::Error::Unsupported),
490 "result was not unsupported (might be implemented now?)"
491 );
492 }
493
494 #[test]
495 fn test_publisher_resume_not_yet_supported_by_c_lib() {
496 let domain_id = crate::tests::domain::unique_id();
497 let domain = crate::Domain::new(domain_id).unwrap();
498 let participant = crate::Participant::new(&domain).unwrap();
499 let publisher = Publisher::new(&participant).unwrap();
500
501 let result = publisher.resume();
502 assert_eq!(
503 result,
504 Err(crate::Error::Unsupported),
505 "result was not unsupported (might be implemented now?)"
506 );
507 }
508
509 #[test]
510 fn test_publisher_wait_for_acks_not_yet_supported_by_c_lib() {
511 let domain_id = crate::tests::domain::unique_id();
512 let domain = crate::Domain::new(domain_id).unwrap();
513 let participant = crate::Participant::new(&domain).unwrap();
514 let publisher = Publisher::new(&participant).unwrap();
515
516 let result =
517 publisher.wait_for_acks(std::time::Duration::from_millis(10).try_into().unwrap());
518 assert_eq!(
519 result,
520 Err(crate::Error::Unsupported),
521 "result was not unsupported (might be implemented now?)"
522 );
523 }
524
525 #[test]
526 fn test_publisher_with_listener() {
527 let domain_id = crate::tests::domain::unique_id();
528 let domain = crate::Domain::new(domain_id).unwrap();
529 let participant = crate::Participant::new(&domain).unwrap();
530
531 let listener = crate::PublisherListener::new();
532
533 let _ = Publisher::new(&participant)
534 .unwrap()
535 .with_listener(listener)
536 .unwrap();
537 let _ = Publisher::builder(&participant)
538 .with_listener(listener)
539 .build()
540 .unwrap();
541
542 let mut publisher = Publisher::new(&participant).unwrap();
543 publisher.set_listener(listener).unwrap();
544 publisher.unset_listener().unwrap();
545 }
546
547 #[test]
548 fn test_publisher_with_listener_on_invalid_publisher() {
549 let domain_id = crate::tests::domain::unique_id();
550 let domain = crate::Domain::new(domain_id).unwrap();
551 let participant = crate::Participant::new(&domain).unwrap();
552
553 let listener = crate::PublisherListener::new();
554
555 let mut publisher = Publisher::new(&participant).unwrap();
556 let publisher_id = publisher.inner;
557 publisher.inner = 0;
558 let result = publisher.set_listener(listener).unwrap_err();
559 assert_eq!(result, crate::Error::BadParameter);
560 let result = publisher.unset_listener().unwrap_err();
561 assert_eq!(result, crate::Error::BadParameter);
562 publisher.inner = publisher_id;
563 }
564}