Skip to main content

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}