cyclonedds/listener.rs
1//! Listener types for reacting to [`status events`](crate::Status) on
2//! [`entities`](crate::entity::Entity).
3//!
4//! Each entity type has a corresponding listener struct that holds optional
5//! callbacks for the status events it can produce. Callbacks are plain function
6//! pointers and are registered via chainable `with_*` methods.
7//!
8//! The listener structure mimics the DDS entity hierarchy. [`Listener`] is the
9//! top-level type attached to a [`Participant`](crate::Participant) and
10//! composes [`SubscriberListener`] and [`PublisherListener`]. Entity-specific
11//! listeners ([`ReaderListener`] and [`WriterListener`]) are attached directly
12//! to their respective entities.
13//!
14//! ```text
15//! ╭───────────────────────╮ ╭─────────────────────────────────────╮
16//! │ Entity │ │ Listener │
17//! ╰───────────────────────╯ ╰─────────────────────────────────────╯
18//!
19//! Domain
20//! │
21//! Participant ··················································· Listener
22//! ├─ Topic<T> ······························ TopicListener<T> ─┤
23//! ├─ Subscriber ··························· SubscriberListener ─┤
24//! │ └─ Reader<T> ··········· ReaderListener<T> ───┘ │
25//! └─ Publisher ····························· PublisherListener ─┘
26//! └─ Writer<T> ············ WriterListener<T> ───┘
27//! ```
28//!
29//! Listeners can be set at any level of the entity hierarchy. A listener set on
30//! a [`Participant`](crate::Participant) will have its callbacks inherited by
31//! child entities of that participant.
32//!
33//! Alternatively, a higher-level listener can also be passed directly to the
34//! child entity's builder (as each listener type implements [`AsRef`] for the
35//! listener types below it in the hierarchy). As a result, a single
36//! [`Listener`] can be reused across multiple entity builders without
37//! constructing separate listeners for each level.
38//!
39//! ```
40//! use cyclonedds::{Domain, Listener, Participant, Subscriber};
41//!
42//! let domain = Domain::default();
43//!
44//! // Create a participant listener with the subscriber callbacks configured.
45//! let listener = Listener::new().with_subscriber(|s| {
46//! s.with_data_on_readers(|subscriber| {
47//! println!("{subscriber:?} has data");
48//! })
49//! });
50//!
51//! // Create a participant with the listener.
52//! let participant = Participant::builder(&domain)
53//! .with_listener(&listener)
54//! .build()?;
55//!
56//! // Subscribers created under the participant will inherit the `data_on_readers`
57//! // callback.
58//! let subscriber = Subscriber::new(&participant)?;
59//!
60//! // This subscriber is explicitly created with the subscriber portion of the
61//! // `listener`.
62//! let subscriber = Subscriber::builder(&participant)
63//! .with_listener(&listener)
64//! .build()?;
65//!
66//! # Ok::<_, cyclonedds::Error>(())
67//! ```
68//!
69//! Each callback fires when its corresponding [`Status`](crate::Status)
70//! condition is triggered. Most callbacks receive a status value from the
71//! [`status`](crate::status) module carrying event-specific detail such as
72//! counts and last-instance handles.
73//!
74//! # Warning
75//!
76//! <div class="warning">
77//!
78//! **Unstable:** The full DDS listener hierarchy, where [`TopicListener<T>`]
79//! composes under [`Listener`] and [`ReaderListener<T>`] and
80//! [`WriterListener<T>`] compose under [`SubscriberListener`] and
81//! [`PublisherListener`], respectively, is not yet implemented.
82//!
83//! The [`Listener`], [`SubscriberListener`], and [`PublisherListener`] may
84//! propagate to many [`Topic<T>`](crate::Topic), [`Reader<T>`](crate::Reader),
85//! and [`Writer<T>`](crate::Writer) that all have different types for `<T>`. As
86//! a result, one of two obvious solutions presents itself:
87//!
88//! - Allow these higher-level types to only have callbacks of effectively [`std::any::Any`] and
89//! require the callback to attempt to convert. This maps the most correctly onto how the API is
90//! designed in the specification but would greatly complicate the internal dispatching of these
91//! listeners.
92//!
93//! - Maintain a typed registry of all the different types of callbacks that are attached on the
94//! higher-level untyped subscribers and then add code to check if the event that fired
95//! corresponds to a type whose callback was registered. This would work but introduces semantics
96//! that do not match the other DDS implementations.
97//!
98//! </div>
99//!
100//! # Examples
101//!
102//! ```
103//! use cyclonedds::entity::Entity;
104//! use cyclonedds::{Reader, ReaderListener, Topic, TopicListener, Writer, WriterListener};
105//! # #[derive(
106//! # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
107//! # )]
108//! # struct Data {
109//! # x: i32,
110//! # }
111//! # let domain = cyclonedds::Domain::default();
112//! # let participant = cyclonedds::Participant::new(&domain)?;
113//!
114//! let topic = Topic::<Data>::builder(&participant, "Example")
115//! .with_listener(
116//! TopicListener::new().with_inconsistent_topic(|topic, inconsistent_topic| {
117//! println!(
118//! "{topic:?} inconsistent topic: {} just encountered, {} encountered in total",
119//! inconsistent_topic.total.delta, inconsistent_topic.total.count
120//! )
121//! }),
122//! )
123//! .build()?;
124//!
125//! let reader = Reader::builder(&topic)
126//! .with_listener(
127//! ReaderListener::new()
128//! .with_subscription_matched(|reader, subscription_matched| {
129//! println!("{reader:?} had a subscription match: {subscription_matched:?}")
130//! })
131//! .with_sample_lost(|reader, sample_lost| {
132//! println!(
133//! "{reader:?} lost samples: {} just lost, {} lost in total",
134//! sample_lost.total.delta, sample_lost.total.count
135//! )
136//! }),
137//! )
138//! .build()?;
139//!
140//! let writer = Writer::builder(&topic)
141//! .with_listener(
142//! WriterListener::new()
143//! .with_publication_matched(|writer, publication_matched| {
144//! println!("{writer:?} has a publication match: {publication_matched:?}")
145//! })
146//! .with_liveliness_lost(|writer, liveliness_lost| {
147//! println!(
148//! "{writer:?} liveliness lost: {} just lost, {} lost in total",
149//! liveliness_lost.total.delta, liveliness_lost.total.count
150//! )
151//! }),
152//! )
153//! .build()?;
154//! # Ok::<_, cyclonedds::Error>(())
155//! ```
156
157use crate::Result;
158use crate::internal::ffi;
159use crate::internal::traits::AsFfi;
160use crate::status::{
161 InconsistentTopic, LivelinessChanged, LivelinessLost, OfferedDeadlineMissed,
162 OfferedIncompatibleQoS, PublicationMatched, RequestedDeadlineMissed, RequestedIncompatibleQoS,
163 SampleLost, SampleRejected, SubscriptionMatched,
164};
165
166/// Listener attached to a [`Participant`](crate::Participant).
167///
168/// In the DDS entity hierarchy this composes [`SubscriberListener`],
169/// [`PublisherListener`], and [`TopicListener`]. When attached to a
170/// participant, entities created under it inherit any of the configured
171/// callbacks that apply to that entity type.
172///
173/// # Examples
174///
175/// ```
176/// use cyclonedds::{Domain, Listener, Participant, Subscriber};
177///
178/// let domain = Domain::default();
179/// let listener = Listener::new().with_subscriber(|subscriber_listener| {
180/// subscriber_listener
181/// .with_data_on_readers(|subscriber| println!("{subscriber:?} has data on readers"))
182/// });
183/// let participant = Participant::builder(&domain)
184/// .with_listener(&listener)
185/// .build()?;
186///
187/// // This subscriber inherits the callbacks set on the `participant` via the `listener`.
188/// let subscriber = Subscriber::new(&participant)?;
189///
190/// // This subscriber will have the subscriber subset associated with the `listener` directly
191/// // applied to it.
192/// let subscriber = Subscriber::builder(&participant)
193/// .with_listener(&listener)
194/// .build()?;
195/// # Ok::<_, cyclonedds::Error>(())
196/// ```
197#[derive(Debug, Default, Clone, Copy)]
198pub struct Listener {
199 // topic: TopicListener<T>,
200 subscriber: SubscriberListener,
201 publisher: PublisherListener,
202}
203
204/// Listener attached to a [`Topic<T>`](crate::Topic<T>).
205#[derive(Debug, Clone, Copy)]
206pub struct TopicListener<T>
207where
208 T: crate::Topicable,
209{
210 inconsistent_topic: Option<fn(&crate::Topic<'_, '_, T>, InconsistentTopic)>,
211}
212
213/// Listener attached to a [`Subscriber`](crate::Subscriber).
214///
215/// <div class="warning">
216///
217/// Currently [`SubscriberListener`] is missing its configuration for composing
218/// a [`ReaderListener<T>`] under this non-generic type. See the [module-level
219/// warning](crate::listener#warning) for more detail.
220///
221/// </div>
222#[derive(Debug, Default, Clone, Copy)]
223pub struct SubscriberListener {
224 data_on_readers: Option<fn(&crate::Subscriber<'_, '_>)>,
225 // ///
226 // pub reader: ReaderListener<T>,
227}
228
229/// Listener attached to a [`Reader<T>`](crate::Reader<T>).
230#[derive(Debug, Clone, Copy)]
231pub struct ReaderListener<T>
232where
233 T: crate::Topicable,
234{
235 sample_lost: Option<fn(&crate::Reader<'_, '_, '_, T>, SampleLost)>,
236 data_available: Option<fn(&crate::Reader<'_, '_, '_, T>)>,
237 sample_rejected: Option<fn(&crate::Reader<'_, '_, '_, T>, SampleRejected)>,
238 liveliness_changed: Option<fn(&crate::Reader<'_, '_, '_, T>, LivelinessChanged)>,
239 requested_deadline_missed: Option<fn(&crate::Reader<'_, '_, '_, T>, RequestedDeadlineMissed)>,
240 requested_incompatible_qos: Option<fn(&crate::Reader<'_, '_, '_, T>, RequestedIncompatibleQoS)>,
241 subscription_matched: Option<fn(&crate::Reader<'_, '_, '_, T>, SubscriptionMatched)>,
242}
243
244/// Listener attached to a [`Publisher`](crate::Publisher).
245///
246/// <div class="warning">
247///
248/// Currently [`PublisherListener`] has no registered callbacks pending a
249/// solution for composing [`WriterListener<T>`] under this non-generic type.
250/// See the [module-level warning](crate::listener#warning) for more detail.
251///
252/// </div>
253#[derive(Debug, Default, Clone, Copy)]
254pub struct PublisherListener {
255 // ///
256 // pub writer: WriterListener<T>,
257}
258
259/// Listener attached to a [`Writer<T>`](crate::Writer<T>).
260#[derive(Debug, Clone, Copy)]
261pub struct WriterListener<T>
262where
263 T: crate::Topicable,
264{
265 liveliness_lost: Option<fn(&crate::Writer<'_, '_, '_, T>, LivelinessLost)>,
266 offered_deadline_missed: Option<fn(&crate::Writer<'_, '_, '_, T>, OfferedDeadlineMissed)>,
267 offered_incompatible_qos: Option<fn(&crate::Writer<'_, '_, '_, T>, OfferedIncompatibleQoS)>,
268 publication_matched: Option<fn(&crate::Writer<'_, '_, '_, T>, PublicationMatched)>,
269}
270
271impl<T> Default for TopicListener<T>
272where
273 T: crate::Topicable,
274{
275 fn default() -> Self {
276 Self {
277 inconsistent_topic: Option::default(),
278 }
279 }
280}
281
282impl<T> Default for ReaderListener<T>
283where
284 T: crate::Topicable,
285{
286 fn default() -> Self {
287 Self {
288 sample_lost: Option::default(),
289 data_available: Option::default(),
290 sample_rejected: Option::default(),
291 liveliness_changed: Option::default(),
292 requested_deadline_missed: Option::default(),
293 requested_incompatible_qos: Option::default(),
294 subscription_matched: Option::default(),
295 }
296 }
297}
298
299impl<T> Default for WriterListener<T>
300where
301 T: crate::Topicable,
302{
303 fn default() -> Self {
304 Self {
305 liveliness_lost: Option::default(),
306 offered_deadline_missed: Option::default(),
307 offered_incompatible_qos: Option::default(),
308 publication_matched: Option::default(),
309 }
310 }
311}
312
313impl Listener {
314 /// Creates a new [`Listener`] with no callbacks registered.
315 ///
316 /// # Examples
317 ///
318 /// ```
319 /// use cyclonedds::Listener;
320 ///
321 /// let listener = Listener::new();
322 /// ```
323 #[must_use]
324 pub fn new() -> Self {
325 Self::default()
326 }
327
328 // ///
329 // pub fn with_topic(mut self, setter: fn(TopicListener<T>) -> TopicListener<T>)
330 // -> Self { self.topic = setter(self.topic);
331 // self
332 // }
333
334 /// Configures the [`SubscriberListener`] via a setter callback.
335 ///
336 /// # Examples
337 ///
338 /// ```
339 /// use cyclonedds::Listener;
340 ///
341 /// let listener = Listener::new().with_subscriber(|s| {
342 /// s.with_data_on_readers(|subscriber| {
343 /// println!("data available on a reader");
344 /// })
345 /// });
346 /// ```
347 #[must_use]
348 pub fn with_subscriber(mut self, setter: fn(SubscriberListener) -> SubscriberListener) -> Self {
349 self.subscriber = setter(self.subscriber);
350 self
351 }
352
353 /// Configures the [`PublisherListener`] via a setter callback.
354 ///
355 /// # Examples
356 ///
357 /// <div class="warning">
358 ///
359 /// This example does not compile because the [`PublisherListener`] does not
360 /// have its `with_writer::<T>` setter yet. This is due to the fact that
361 /// the higher-level listeners are untyped in `<T>` but the lower-level
362 /// listeners are typed in `<T>` and a solution for crossing this boundary
363 /// still needs to be worked out.
364 ///
365 /// See the [module-level warning](crate::listener#warning) for more detail.
366 ///
367 /// </div>
368 ///
369 /// ```ignore
370 /// use cyclonedds::Listener;
371 ///
372 /// let listener = Listener::new().with_publisher(|p| {
373 /// p.with_writer(|w| {
374 /// w.with_publication_matched(|writer, publication_matched| {
375 /// println!("{writer:?} has publication match: {publication_matched:?}")
376 /// })
377 /// })
378 /// });
379 /// ```
380 #[must_use]
381 pub fn with_publisher(mut self, setter: fn(PublisherListener) -> PublisherListener) -> Self {
382 self.publisher = setter(self.publisher);
383 self
384 }
385
386 #[inline]
387 pub(crate) fn apply_listener_ffi(self, listener: &mut ffi::Listener) {
388 // self.topic.apply_listener_ffi(listener);
389 self.subscriber.apply_listener_ffi(listener);
390 self.publisher.apply_listener_ffi(listener);
391 }
392}
393
394impl AsFfi for Listener {
395 type Target<'a> = Result<ffi::Listener>;
396
397 #[inline]
398 fn as_ffi(&self) -> Self::Target<'_> {
399 ffi::Listener::new().map(|mut listener| {
400 self.apply_listener_ffi(&mut listener);
401 listener
402 })
403 }
404}
405
406impl<T> TopicListener<T>
407where
408 T: crate::Topicable,
409{
410 /// Creates a new [`TopicListener<T>`] with no callbacks registered.
411 ///
412 /// # Examples
413 ///
414 /// ```
415 /// use cyclonedds::TopicListener;
416 /// # #[derive(
417 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
418 /// # )]
419 /// # struct Data {
420 /// # x: i32,
421 /// # }
422 ///
423 /// let listener = TopicListener::<Data>::new();
424 /// ```
425 #[must_use]
426 pub fn new() -> Self {
427 Self::default()
428 }
429
430 /// Sets a callback for the
431 /// [`InconsistentTopic` status event](crate::Status::InconsistentTopic).
432 ///
433 /// The callback receives an
434 /// [`InconsistentTopic` metadata struct](InconsistentTopic).
435 ///
436 /// Fired when a remote topic is discovered with the same name but an
437 /// incompatible type or [`QoS`](crate::QoS).
438 ///
439 /// # Examples
440 ///
441 /// ```
442 /// use cyclonedds::listener::TopicListener;
443 /// # #[derive(
444 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
445 /// # )]
446 /// # struct Data {
447 /// # x: i32,
448 /// # }
449 ///
450 /// let listener =
451 /// TopicListener::<Data>::new().with_inconsistent_topic(|topic, inconsistent_topic| {
452 /// println!("inconsistent topic: {inconsistent_topic:?}");
453 /// });
454 /// ```
455 #[must_use]
456 pub fn with_inconsistent_topic(
457 mut self,
458 callback: fn(&crate::Topic<'_, '_, T>, InconsistentTopic),
459 ) -> Self {
460 self.inconsistent_topic = Some(callback);
461 self
462 }
463
464 #[inline]
465 pub(crate) fn apply_listener_ffi(&self, listener: &mut ffi::Listener) {
466 if let Some(callback) = self.inconsistent_topic {
467 ffi::dds_listener_set_inconsistent_topic(listener, callback);
468 }
469 }
470}
471
472impl<T> AsFfi for TopicListener<T>
473where
474 T: crate::Topicable,
475{
476 type Target<'a>
477 = Result<ffi::Listener>
478 where
479 T: 'a;
480
481 #[inline]
482 fn as_ffi(&self) -> Self::Target<'_> {
483 ffi::Listener::new().map(|mut listener| {
484 self.apply_listener_ffi(&mut listener);
485 listener
486 })
487 }
488}
489
490impl SubscriberListener {
491 /// Creates a new [`SubscriberListener`] with no callbacks registered.
492 ///
493 /// # Examples
494 ///
495 /// ```
496 /// use cyclonedds::SubscriberListener;
497 ///
498 /// let listener = SubscriberListener::new();
499 /// ```
500 #[must_use]
501 pub fn new() -> Self {
502 Self::default()
503 }
504
505 // ///
506 // pub fn with_reader(mut self, setter: fn(ReaderListener<T>) ->
507 // ReaderListener<T>) -> Self { self.reader = setter(self.reader);
508 // self
509 // }
510
511 /// Sets a callback for the [`DataOnReaders` status
512 /// event](crate::Status::DataOnReaders).
513 ///
514 /// Fired when new data is available on one or more readers belonging to
515 /// this subscriber.
516 ///
517 /// # Examples
518 ///
519 /// ```
520 /// use cyclonedds::SubscriberListener;
521 ///
522 /// let listener = SubscriberListener::new().with_data_on_readers(|subscriber| {
523 /// println!("data available on {subscriber:?}");
524 /// });
525 /// ```
526 #[must_use]
527 pub fn with_data_on_readers(mut self, callback: fn(&crate::Subscriber<'_, '_>)) -> Self {
528 self.data_on_readers = Some(callback);
529 self
530 }
531
532 #[inline]
533 pub(crate) fn apply_listener_ffi(self, listener: &mut ffi::Listener) {
534 if let Some(callback) = self.data_on_readers {
535 ffi::dds_listener_set_data_on_readers(listener, callback);
536 }
537 // self.reader.apply_listener_ffi(listener);
538 }
539}
540
541impl AsFfi for SubscriberListener {
542 type Target<'a> = Result<ffi::Listener>;
543
544 #[inline]
545 fn as_ffi(&self) -> Self::Target<'_> {
546 ffi::Listener::new().map(|mut listener| {
547 self.apply_listener_ffi(&mut listener);
548 listener
549 })
550 }
551}
552
553impl PublisherListener {
554 /// Creates a new [`PublisherListener`] with no callbacks registered.
555 ///
556 /// # Examples
557 ///
558 /// ```
559 /// use cyclonedds::PublisherListener;
560 ///
561 /// let listener = PublisherListener::new();
562 /// ```
563 #[must_use]
564 pub fn new() -> Self {
565 Self::default()
566 }
567
568 // ///
569 // pub fn with_writer(mut self, setter: fn(WriterListener<T>) ->
570 // WriterListener<T>) -> Self { self.writer = setter(self.writer);
571 // self
572 // }
573
574 #[inline]
575 pub(crate) const fn apply_listener_ffi(self, listener: &mut ffi::Listener) {
576 let _ = self;
577 let _ = listener;
578 // self.writer.apply_listener_ffi(listener);
579 }
580}
581
582impl AsFfi for PublisherListener {
583 type Target<'a> = Result<ffi::Listener>;
584
585 #[inline]
586 fn as_ffi(&self) -> Self::Target<'_> {
587 ffi::Listener::new().map(|mut listener| {
588 self.apply_listener_ffi(&mut listener);
589 listener
590 })
591 }
592}
593
594impl<T> ReaderListener<T>
595where
596 T: crate::Topicable,
597{
598 /// Creates a new [`ReaderListener<T>`] with no callbacks registered.
599 ///
600 /// # Examples
601 ///
602 /// ```
603 /// use cyclonedds::listener::ReaderListener;
604 /// # #[derive(
605 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
606 /// # )]
607 /// # struct Data {
608 /// # x: i32,
609 /// # }
610 ///
611 /// let listener = ReaderListener::<Data>::new();
612 /// ```
613 #[must_use]
614 pub fn new() -> Self {
615 Self::default()
616 }
617
618 /// Sets a callback for the [`SampleLost` status
619 /// event](crate::Status::SampleLost).
620 ///
621 /// The callback receives a [`SampleLost` metadata struct](SampleLost).
622 ///
623 /// Fired when a sample is lost, meaning it was never received by this
624 /// reader due to resource limits or [`QoS`](crate::QoS) constraints.
625 ///
626 /// # Examples
627 ///
628 /// ```
629 /// use cyclonedds::listener::ReaderListener;
630 /// # #[derive(
631 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
632 /// # )]
633 /// # struct Data {
634 /// # x: i32,
635 /// # }
636 ///
637 /// let listener = ReaderListener::<Data>::new().with_sample_lost(|reader, sample_lost| {
638 /// println!("samples lost: {}", sample_lost.total.count);
639 /// });
640 /// ```
641 #[must_use]
642 pub fn with_sample_lost(
643 mut self,
644 callback: fn(&crate::Reader<'_, '_, '_, T>, SampleLost),
645 ) -> Self {
646 self.sample_lost = Some(callback);
647 self
648 }
649
650 /// Sets a callback for the [`DataAvailable` status
651 /// event](crate::Status::DataAvailable).
652 ///
653 /// Fired when new data is available to be [`peeked`](crate::Reader::peek),
654 /// [`read`](crate::Reader::read), or [`taken`](crate::Reader::take) from
655 /// this reader.
656 ///
657 /// # Examples
658 ///
659 /// ```
660 /// use cyclonedds::listener::ReaderListener;
661 /// # #[derive(
662 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
663 /// # )]
664 /// # struct Data {
665 /// # x: i32,
666 /// # }
667 ///
668 /// let listener = ReaderListener::<Data>::new().with_data_available(|reader| {
669 /// println!("data available on {reader:?}");
670 /// });
671 /// ```
672 #[must_use]
673 pub fn with_data_available(mut self, callback: fn(&crate::Reader<'_, '_, '_, T>)) -> Self {
674 self.data_available = Some(callback);
675 self
676 }
677
678 /// Sets a callback for the
679 /// [`SampleRejected` status event](crate::Status::SampleRejected).
680 ///
681 /// The callback receives a [`SampleRejected` metadata
682 /// struct](SampleRejected).
683 ///
684 /// Fired when an incoming sample is rejected due to
685 /// [`ResourceLimits`](crate::qos::policy::ResourceLimits).
686 ///
687 /// # Examples
688 ///
689 /// ```
690 /// use cyclonedds::listener::ReaderListener;
691 /// # #[derive(
692 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
693 /// # )]
694 /// # struct Data {
695 /// # x: i32,
696 /// # }
697 /// let listener = ReaderListener::<Data>::new().with_sample_rejected(|reader, status| {
698 /// println!("sample rejected: {status:?}");
699 /// });
700 /// ```
701 #[must_use]
702 pub fn with_sample_rejected(
703 mut self,
704 callback: fn(&crate::Reader<'_, '_, '_, T>, SampleRejected),
705 ) -> Self {
706 self.sample_rejected = Some(callback);
707 self
708 }
709
710 /// Sets a callback for the
711 /// [`LivelinessChanged` status event](crate::Status::LivelinessChanged).
712 ///
713 /// The callback receives a
714 /// [`LivelinessChanged` metadata struct](LivelinessChanged).
715 ///
716 /// Fired when the [`Liveliness`](crate::qos::policy::Liveliness) of a
717 /// matched writer changes, i.e. a writer becomes active or inactive.
718 ///
719 /// # Examples
720 ///
721 /// ```
722 /// use cyclonedds::listener::ReaderListener;
723 /// # #[derive(
724 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
725 /// # )]
726 /// # struct Data {
727 /// # x: i32,
728 /// # }
729 ///
730 /// let listener =
731 /// ReaderListener::<Data>::new().with_liveliness_changed(|reader, liveliness_changed| {
732 /// println!("active writers: {}", liveliness_changed.alive.count);
733 /// });
734 /// ```
735 #[must_use]
736 pub fn with_liveliness_changed(
737 mut self,
738 callback: fn(&crate::Reader<'_, '_, '_, T>, LivelinessChanged),
739 ) -> Self {
740 self.liveliness_changed = Some(callback);
741 self
742 }
743
744 /// Sets a callback for the
745 /// [`RequestedDeadlineMissed` status
746 /// event](crate::Status::RequestedDeadlineMissed).
747 ///
748 /// The callback receives a
749 /// [`RequestedDeadlineMissed` metadata struct](RequestedDeadlineMissed).
750 ///
751 /// Fired when a sample is not received within the
752 /// [`Deadline`](crate::qos::policy::Deadline) period offered by a matched
753 /// writer.
754 ///
755 /// # Examples
756 ///
757 /// ```
758 /// use cyclonedds::listener::ReaderListener;
759 /// # #[derive(
760 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
761 /// # )]
762 /// # struct Data {
763 /// # x: i32,
764 /// # }
765 ///
766 /// let listener = ReaderListener::<Data>::new().with_requested_deadline_missed(
767 /// |reader, requested_deadline_missed| {
768 /// println!("deadline missed: {}", requested_deadline_missed.total.count);
769 /// },
770 /// );
771 /// ```
772 #[must_use]
773 pub fn with_requested_deadline_missed(
774 mut self,
775 callback: fn(&crate::Reader<'_, '_, '_, T>, RequestedDeadlineMissed),
776 ) -> Self {
777 self.requested_deadline_missed = Some(callback);
778 self
779 }
780
781 /// Sets a callback for the
782 /// [`RequestedIncompatibleQoS` status
783 /// event](crate::Status::RequestedIncompatibleQoS).
784 ///
785 /// The callback receives a
786 /// [`RequestedIncompatibleQoS` metadata struct](RequestedIncompatibleQoS).
787 ///
788 /// Fired when a writer is discovered whose offered [`QoS`](crate::QoS) is
789 /// incompatible with this reader's requested [`QoS`](crate::QoS).
790 ///
791 /// # Examples
792 ///
793 /// ```
794 /// use cyclonedds::listener::ReaderListener;
795 /// # #[derive(
796 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
797 /// # )]
798 /// # struct Data {
799 /// # x: i32,
800 /// # }
801 ///
802 /// let listener = ReaderListener::<Data>::new().with_requested_incompatible_qos(
803 /// |reader, requested_incompatible_qos| {
804 /// println!("incompatible QoS: {requested_incompatible_qos:?}");
805 /// },
806 /// );
807 /// ```
808 #[must_use]
809 pub fn with_requested_incompatible_qos(
810 mut self,
811 callback: fn(&crate::Reader<'_, '_, '_, T>, RequestedIncompatibleQoS),
812 ) -> Self {
813 self.requested_incompatible_qos = Some(callback);
814 self
815 }
816
817 /// Sets a callback for the
818 /// [`SubscriptionMatched` status event](crate::Status::SubscriptionMatched)
819 /// status event.
820 ///
821 /// The callback receives a
822 /// [`SubscriptionMatched` metadata struct](SubscriptionMatched).
823 ///
824 /// Fired when a writer matching this reader's topic and [`QoS`](crate::QoS)
825 /// is discovered or lost.
826 ///
827 /// # Examples
828 ///
829 /// ```
830 /// use cyclonedds::listener::ReaderListener;
831 /// # #[derive(
832 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
833 /// # )]
834 /// # struct Data {
835 /// # x: i32,
836 /// # }
837 ///
838 /// let listener =
839 /// ReaderListener::<Data>::new().with_subscription_matched(|reader, subscription_matched| {
840 /// println!("matched writers: {}", subscription_matched.current.count);
841 /// });
842 /// ```
843 #[must_use]
844 pub fn with_subscription_matched(
845 mut self,
846 callback: fn(&crate::Reader<'_, '_, '_, T>, SubscriptionMatched),
847 ) -> Self {
848 self.subscription_matched = Some(callback);
849 self
850 }
851
852 #[inline]
853 pub(crate) fn apply_listener_ffi(&self, listener: &mut ffi::Listener) {
854 if let Some(callback) = self.sample_lost {
855 ffi::dds_listener_set_sample_lost(listener, callback);
856 }
857 if let Some(callback) = self.data_available {
858 ffi::dds_listener_set_data_available(listener, callback);
859 }
860 if let Some(callback) = self.sample_rejected {
861 ffi::dds_listener_set_sample_rejected(listener, callback);
862 }
863 if let Some(callback) = self.liveliness_changed {
864 ffi::dds_listener_set_liveliness_changed(listener, callback);
865 }
866 if let Some(callback) = self.requested_deadline_missed {
867 ffi::dds_listener_set_requested_deadline_missed(listener, callback);
868 }
869 if let Some(callback) = self.requested_incompatible_qos {
870 ffi::dds_listener_set_requested_incompatible_qos(listener, callback);
871 }
872 if let Some(callback) = self.subscription_matched {
873 ffi::dds_listener_set_subscription_matched(listener, callback);
874 }
875 }
876}
877
878impl<T> AsFfi for ReaderListener<T>
879where
880 T: crate::Topicable,
881{
882 type Target<'a>
883 = Result<ffi::Listener>
884 where
885 T: 'a;
886
887 #[inline]
888 fn as_ffi(&self) -> Self::Target<'_> {
889 ffi::Listener::new().map(|mut listener| {
890 self.apply_listener_ffi(&mut listener);
891 listener
892 })
893 }
894}
895
896impl<T> WriterListener<T>
897where
898 T: crate::Topicable,
899{
900 /// Creates a new [`WriterListener<T>`] with no callbacks registered.
901 ///
902 /// # Examples
903 ///
904 /// ```
905 /// use cyclonedds::TopicListener;
906 /// # #[derive(
907 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
908 /// # )]
909 /// # struct Data {
910 /// # x: i32,
911 /// # }
912 ///
913 /// let listener = TopicListener::<Data>::new();
914 /// ```
915 #[must_use]
916 pub fn new() -> Self {
917 Self::default()
918 }
919
920 /// Sets a callback for the
921 /// [`LivelinessLost` status event](crate::Status::LivelinessLost).
922 ///
923 /// The callback receives a [`LivelinessLost` metadata
924 /// struct](LivelinessLost).
925 ///
926 /// Fired when the writer fails to meet its
927 /// [`Liveliness`](crate::qos::policy::Liveliness) policy and is considered
928 /// inactive by matched readers.
929 ///
930 /// # Examples
931 ///
932 /// ```
933 /// use cyclonedds::listener::WriterListener;
934 /// # #[derive(
935 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
936 /// # )]
937 /// # struct Data {
938 /// # x: i32,
939 /// # }
940 ///
941 /// let listener = WriterListener::<Data>::new().with_liveliness_lost(|writer, liveliness_lost| {
942 /// println!(
943 /// "{writer:?} liveliness lost: {}",
944 /// liveliness_lost.total.count
945 /// );
946 /// });
947 /// ```
948 #[must_use]
949 pub fn with_liveliness_lost(
950 mut self,
951 callback: fn(&crate::Writer<'_, '_, '_, T>, LivelinessLost),
952 ) -> Self {
953 self.liveliness_lost = Some(callback);
954 self
955 }
956
957 /// Sets a callback for the
958 /// [`OfferedDeadlineMissed` status
959 /// event](crate::Status::OfferedDeadlineMissed) status event.
960 ///
961 /// The callback receives an
962 /// [`OfferedDeadlineMissed` metadata struct](OfferedDeadlineMissed).
963 ///
964 /// Fired when the writer fails to write a new sample within its offered
965 /// [`Deadline`](crate::qos::policy::Deadline) period for one or more
966 /// instances.
967 ///
968 /// # Examples
969 ///
970 /// ```
971 /// use cyclonedds::listener::WriterListener;
972 /// # #[derive(
973 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
974 /// # )]
975 /// # struct Data {
976 /// # x: i32,
977 /// # }
978 ///
979 /// let listener = WriterListener::<Data>::new().with_offered_deadline_missed(
980 /// |writer, offered_deadline_missed| {
981 /// println!(
982 /// "{writer:?} deadline missed: {}",
983 /// offered_deadline_missed.total.count
984 /// );
985 /// },
986 /// );
987 /// ```
988 #[must_use]
989 pub fn with_offered_deadline_missed(
990 mut self,
991 callback: fn(&crate::Writer<'_, '_, '_, T>, OfferedDeadlineMissed),
992 ) -> Self {
993 self.offered_deadline_missed = Some(callback);
994 self
995 }
996
997 /// Sets a callback for the
998 /// [`OfferedIncompatibleQoS` status
999 /// event](crate::Status::OfferedIncompatibleQoS) status event.
1000 ///
1001 /// The callback receives an
1002 /// [`OfferedIncompatibleQoS` metadata struct](OfferedIncompatibleQoS).
1003 ///
1004 /// Fired when a reader is discovered whose requested [`QoS`](crate::QoS) is
1005 /// incompatible with this writer's offered [`QoS`](crate::QoS).
1006 ///
1007 /// # Examples
1008 ///
1009 /// ```
1010 /// use cyclonedds::listener::WriterListener;
1011 /// # #[derive(
1012 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
1013 /// # )]
1014 /// # struct Data {
1015 /// # x: i32,
1016 /// # }
1017 ///
1018 /// let listener = WriterListener::<Data>::new().with_offered_incompatible_qos(
1019 /// |writer, offered_incompatible_qos| {
1020 /// println!("{writer:?} discovered incompatible QoS: {offered_incompatible_qos:?}");
1021 /// },
1022 /// );
1023 /// ```
1024 #[must_use]
1025 pub fn with_offered_incompatible_qos(
1026 mut self,
1027 callback: fn(&crate::Writer<'_, '_, '_, T>, OfferedIncompatibleQoS),
1028 ) -> Self {
1029 self.offered_incompatible_qos = Some(callback);
1030 self
1031 }
1032
1033 /// Sets a callback for the
1034 /// [`PublicationMatched` status event](crate::Status::PublicationMatched).
1035 ///
1036 /// The callback receives a
1037 /// [`PublicationMatched` metadata struct](PublicationMatched).
1038 ///
1039 /// Fired when a reader matching this writer's topic and [`QoS`](crate::QoS)
1040 /// is discovered.
1041 ///
1042 /// # Examples
1043 ///
1044 /// ```
1045 /// use cyclonedds::listener::WriterListener;
1046 /// # #[derive(
1047 /// # cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
1048 /// # )]
1049 /// # struct Data {
1050 /// # x: i32,
1051 /// # }
1052 ///
1053 /// let listener = WriterListener::<Data>::new().with_publication_matched(|writer, status| {
1054 /// println!("{writer:?} matched readers: {}", status.current.count);
1055 /// });
1056 /// ```
1057 #[must_use]
1058 pub fn with_publication_matched(
1059 mut self,
1060 callback: fn(&crate::Writer<'_, '_, '_, T>, PublicationMatched),
1061 ) -> Self
1062 where
1063 T: crate::Topicable,
1064 {
1065 self.publication_matched = Some(callback);
1066 self
1067 }
1068
1069 #[inline]
1070 pub(crate) fn apply_listener_ffi(&self, listener: &mut ffi::Listener) {
1071 if let Some(callback) = self.liveliness_lost {
1072 ffi::dds_listener_set_liveliness_lost(listener, callback);
1073 }
1074 if let Some(callback) = self.offered_deadline_missed {
1075 ffi::dds_listener_set_offered_deadline_missed(listener, callback);
1076 }
1077 if let Some(callback) = self.offered_incompatible_qos {
1078 ffi::dds_listener_set_offered_incompatible_qos(listener, callback);
1079 }
1080 if let Some(callback) = self.publication_matched {
1081 ffi::dds_listener_set_publication_matched(listener, callback);
1082 }
1083 }
1084}
1085
1086impl<T> AsFfi for WriterListener<T>
1087where
1088 T: crate::Topicable,
1089{
1090 type Target<'a>
1091 = Result<ffi::Listener>
1092 where
1093 T: 'a;
1094
1095 #[inline]
1096 fn as_ffi(&self) -> Self::Target<'_> {
1097 ffi::Listener::new().map(|mut listener| {
1098 self.apply_listener_ffi(&mut listener);
1099 listener
1100 })
1101 }
1102}
1103
1104impl<T> AsRef<ReaderListener<T>> for ReaderListener<T>
1105where
1106 T: crate::Topicable,
1107{
1108 fn as_ref(&self) -> &ReaderListener<T> {
1109 self
1110 }
1111}
1112impl<T> AsRef<WriterListener<T>> for WriterListener<T>
1113where
1114 T: crate::Topicable,
1115{
1116 fn as_ref(&self) -> &WriterListener<T> {
1117 self
1118 }
1119}
1120impl AsRef<SubscriberListener> for SubscriberListener {
1121 fn as_ref(&self) -> &SubscriberListener {
1122 self
1123 }
1124}
1125impl AsRef<PublisherListener> for PublisherListener {
1126 fn as_ref(&self) -> &PublisherListener {
1127 self
1128 }
1129}
1130impl<T> AsRef<TopicListener<T>> for TopicListener<T>
1131where
1132 T: crate::Topicable,
1133{
1134 fn as_ref(&self) -> &TopicListener<T> {
1135 self
1136 }
1137}
1138impl AsRef<Listener> for Listener {
1139 fn as_ref(&self) -> &Listener {
1140 self
1141 }
1142}
1143
1144// impl<T> AsRef<ReaderListener<T>> for Listener<T> {
1145// fn as_ref(&self) -> &ReaderListener<T> {
1146// &self.subscriber.reader
1147// }
1148// }
1149// impl<T> AsRef<WriterListener<T>> for Listener<T> {
1150// fn as_ref(&self) -> &WriterListener<T> {
1151// &self.publisher.writer
1152// }
1153// }
1154impl AsRef<SubscriberListener> for Listener {
1155 fn as_ref(&self) -> &SubscriberListener {
1156 &self.subscriber
1157 }
1158}
1159impl AsRef<PublisherListener> for Listener {
1160 fn as_ref(&self) -> &PublisherListener {
1161 &self.publisher
1162 }
1163}
1164// impl<T> AsRef<TopicListener<T>> for Listener<T> {
1165// fn as_ref(&self) -> &TopicListener<T> {
1166// &self.topic
1167// }
1168// }
1169
1170// impl<T> AsRef<ReaderListener<T>> for SubscriberListener<T> {
1171// fn as_ref(&self) -> &ReaderListener<T> {
1172// &self.reader
1173// }
1174// }
1175// impl<T> AsRef<WriterListener<T>> for PublisherListener<T> {
1176// fn as_ref(&self) -> &WriterListener<T> {
1177// &self.writer
1178// }
1179// }
1180
1181#[cfg(test)]
1182mod tests {
1183 use super::*;
1184 use crate::Topicable;
1185
1186 fn receive_listener<L>(listener: L)
1187 where
1188 L: AsRef<Listener>,
1189 {
1190 let _ = listener.as_ref();
1191 }
1192
1193 fn receive_topic_listener<L, T>(listener: L)
1194 where
1195 L: AsRef<TopicListener<T>>,
1196 T: crate::Topicable,
1197 {
1198 let _ = listener.as_ref();
1199 }
1200
1201 fn receive_subscriber_listener<L>(listener: L)
1202 where
1203 L: AsRef<SubscriberListener>,
1204 {
1205 let _ = listener.as_ref();
1206 }
1207
1208 fn receive_publisher_listener<L>(listener: L)
1209 where
1210 L: AsRef<PublisherListener>,
1211 {
1212 let _ = listener.as_ref();
1213 }
1214
1215 fn receive_reader_listener<L, T>(listener: L)
1216 where
1217 L: AsRef<ReaderListener<T>>,
1218 T: crate::Topicable,
1219 {
1220 let _ = listener.as_ref();
1221 }
1222
1223 fn receive_writer_listener<L, T>(listener: L)
1224 where
1225 L: AsRef<WriterListener<T>>,
1226 T: crate::Topicable,
1227 {
1228 let _ = listener.as_ref();
1229 }
1230
1231 #[test]
1232 fn test_listener_create() {
1233 let listener = Listener::new()
1234 // .with_topic(|topic| topic.with_inconsistent_topic(|_, _| ()))
1235 .with_subscriber(|subscriber| {
1236 subscriber.with_data_on_readers(|_| ())
1237 // .with_reader(|reader| {
1238 // reader
1239 // .with_data_available(|_| ())
1240 // .with_liveliness_changed(|_, _| ())
1241 // .with_requested_deadline_missed(|_, _| ())
1242 // .with_requested_incompatible_qos(|_, _| ())
1243 // .with_sample_lost(|_, _| ())
1244 // .with_sample_rejected(|_, _| ())
1245 // .with_subscription_matched(|_, _| ())
1246 // })
1247 })
1248 .with_publisher(|publisher| {
1249 publisher
1250 // .with_writer(|writer| {
1251 // writer
1252 // .with_liveliness_lost(|_, _| ())
1253 // .with_offered_deadline_missed(|_, _| ())
1254 // .with_offered_incompatible_qos(|_, _| ())
1255 // .with_publication_matched(|_, _| ())
1256 // })
1257 });
1258 let topic_listener =
1259 TopicListener::<crate::tests::topic::Data>::new().with_inconsistent_topic(|_, _| ());
1260 let subscriber_listener = SubscriberListener::new()
1261 .with_data_on_readers(|_| ())
1262 // .with_reader(|reader| {
1263 // reader
1264 // .with_data_available(|_| ())
1265 // .with_liveliness_changed(|_, _| ())
1266 // .with_requested_deadline_missed(|_, _| ())
1267 // .with_requested_incompatible_qos(|_, _| ())
1268 // .with_sample_lost(|_, _| ())
1269 // .with_sample_rejected(|_, _| ())
1270 // .with_subscription_matched(|_, _| ())
1271 // })
1272 ;
1273 let publisher_listener =
1274 PublisherListener::new()
1275 // .with_writer(|writer| {
1276 // writer
1277 // .with_liveliness_lost(|_, _| ())
1278 // .with_offered_deadline_missed(|_, _| ())
1279 // .with_offered_incompatible_qos(|_, _| ())
1280 // .with_publication_matched(|_, _| ())
1281 // })
1282 ;
1283 let reader_listener = ReaderListener::<crate::tests::topic::Data>::new()
1284 .with_data_available(|_| ())
1285 .with_liveliness_changed(|_, _| ())
1286 .with_requested_deadline_missed(|_, _| ())
1287 .with_requested_incompatible_qos(|_, _| ())
1288 .with_sample_lost(|_, _| ())
1289 .with_sample_rejected(|_, _| ())
1290 .with_subscription_matched(|_, _| ());
1291 let writer_listener = WriterListener::<crate::tests::topic::Data>::new()
1292 .with_liveliness_lost(|_, _| ())
1293 .with_offered_deadline_missed(|_, _| ())
1294 .with_offered_incompatible_qos(|_, _| ())
1295 .with_publication_matched(|_, _| ());
1296
1297 receive_listener(listener);
1298
1299 receive_topic_listener(&topic_listener);
1300 // receive_topic_listener(&listener);
1301
1302 receive_subscriber_listener(subscriber_listener);
1303 receive_subscriber_listener(listener);
1304
1305 receive_publisher_listener(publisher_listener);
1306 receive_publisher_listener(listener);
1307
1308 receive_reader_listener(&reader_listener);
1309 // receive_reader_listener(&subscriber_listener);
1310 // receive_reader_listener(&listener);
1311
1312 receive_writer_listener(&writer_listener);
1313 // receive_writer_listener(&publisher_listener);
1314 // receive_writer_listener(&listener);
1315 }
1316
1317 #[test]
1318 fn test_subscriber_listener_callbacks() {
1319 #[derive(Debug, PartialEq)]
1320 struct Triggered {
1321 data_on_readers: u32,
1322 }
1323
1324 static TRIGGERED: std::sync::Mutex<Triggered> =
1325 std::sync::Mutex::new(Triggered { data_on_readers: 0 });
1326
1327 let domain_id = crate::tests::domain::unique_id();
1328 let topic_name = crate::tests::topic::unique_name();
1329 let domain = crate::Domain::new(domain_id).unwrap();
1330
1331 let participant = crate::Participant::new(&domain).unwrap();
1332 let topic =
1333 crate::Topic::<crate::tests::topic::Data>::new(&participant, &topic_name).unwrap();
1334 let subscriber = crate::Subscriber::builder(&participant)
1335 .with_listener(
1336 crate::SubscriberListener::new().with_data_on_readers(|_subscriber| {
1337 TRIGGERED.lock().unwrap().data_on_readers += 1;
1338 }),
1339 )
1340 .build()
1341 .unwrap();
1342 let reader = crate::Reader::builder(&topic)
1343 .with_subscriber(&subscriber)
1344 .build()
1345 .unwrap();
1346 let writer = crate::Writer::new(&topic).unwrap();
1347
1348 let sample = crate::tests::topic::Data::default();
1349 writer.write(&sample).unwrap();
1350
1351 let samples = reader.read().unwrap();
1352 assert_eq!(samples.len(), 1);
1353
1354 assert_eq!(*samples[0], sample);
1355
1356 assert_eq!(*TRIGGERED.lock().unwrap(), Triggered { data_on_readers: 1 });
1357 }
1358
1359 #[test]
1360 fn test_publisher_listener_callbacks() {
1361 let domain_id = crate::tests::domain::unique_id();
1362 let topic_name = crate::tests::topic::unique_name();
1363 let domain = crate::Domain::new(domain_id).unwrap();
1364
1365 let participant = crate::Participant::new(&domain).unwrap();
1366 let topic =
1367 crate::Topic::<crate::tests::topic::Data>::new(&participant, &topic_name).unwrap();
1368 let publisher = crate::Publisher::builder(&participant)
1369 .with_listener(crate::PublisherListener::new())
1370 .build()
1371 .unwrap();
1372 let reader = crate::Reader::new(&topic).unwrap();
1373 let writer = crate::Writer::builder(&topic)
1374 .with_publisher(&publisher)
1375 .build()
1376 .unwrap();
1377
1378 let sample = crate::tests::topic::Data::default();
1379 writer.write(&sample).unwrap();
1380
1381 let samples = reader.read().unwrap();
1382 assert_eq!(samples.len(), 1);
1383
1384 assert_eq!(*samples[0], sample);
1385 }
1386
1387 #[test]
1388 fn test_reader_listener_callbacks() {
1389 #[derive(Debug, PartialEq)]
1390 struct Triggered {
1391 requested_incompatible_qos: u32,
1392 requested_deadline_missed: bool,
1393 sample_rejected: u32,
1394 data_available: u32,
1395 subscription_matched: u32,
1396 liveliness_changed: u32,
1397 sample_lost: u32,
1398 }
1399
1400 static TRIGGERED: std::sync::Mutex<Triggered> = std::sync::Mutex::new(Triggered {
1401 requested_incompatible_qos: 0,
1402 requested_deadline_missed: false,
1403 sample_rejected: 0,
1404 data_available: 0,
1405 subscription_matched: 0,
1406 liveliness_changed: 0,
1407 sample_lost: 0,
1408 });
1409
1410 let domain_id = crate::tests::domain::unique_id();
1411 let topic_name = crate::tests::topic::unique_name();
1412 let domain = crate::Domain::new(domain_id).unwrap();
1413
1414 let participant = crate::Participant::new(&domain).unwrap();
1415 let qos = crate::QoS::new()
1416 .with_destination_order(crate::qos::policy::DestinationOrder::BySourceTimestamp);
1417 let topic = crate::Topic::<crate::tests::topic::Data>::builder(&participant, &topic_name)
1418 .with_qos(&qos)
1419 .build()
1420 .unwrap();
1421
1422 {
1423 let _writer = crate::Writer::new(&topic).unwrap();
1424 let _reader = crate::Reader::builder(&topic)
1425 .with_qos(
1426 &crate::QoS::new().with_durability(crate::qos::policy::Durability::Persistent),
1427 )
1428 .with_listener(
1429 crate::ReaderListener::new().with_requested_incompatible_qos(
1430 |_reader, _metadata| {
1431 TRIGGERED.lock().unwrap().requested_incompatible_qos += 1;
1432 },
1433 ),
1434 )
1435 .build()
1436 .unwrap();
1437 }
1438
1439 {
1440 let qos = crate::QoS::new().with_deadline(crate::qos::policy::Deadline {
1441 period: crate::Duration::from_nanos(1_000_000),
1442 });
1443 let reader = crate::Reader::builder(&topic)
1444 .with_qos(&qos)
1445 .with_listener(crate::ReaderListener::new().with_requested_deadline_missed(
1446 |_reader, _metadata| {
1447 TRIGGERED.lock().unwrap().requested_deadline_missed |= true;
1448 },
1449 ))
1450 .build()
1451 .unwrap();
1452 let writer = crate::Writer::builder(&topic)
1453 .with_qos(&qos)
1454 .build()
1455 .unwrap();
1456
1457 let sample = crate::tests::topic::Data::default();
1458 writer.write(&sample).unwrap();
1459
1460 let samples = reader.take().unwrap();
1461 assert_eq!(samples.len(), 1);
1462 assert_eq!(*samples[0], sample);
1463
1464 while !TRIGGERED.lock().unwrap().requested_deadline_missed {
1465 std::thread::sleep(std::time::Duration::from_nanos(50));
1466 }
1467 }
1468
1469 {
1470 let reader = crate::Reader::builder(&topic)
1471 .with_qos(&crate::QoS::new().with_resource_limits(
1472 crate::qos::policy::ResourceLimits {
1473 max_samples: crate::qos::policy::ResourceLimit::Unlimited,
1474 max_instances: crate::qos::policy::ResourceLimit::Limited(1),
1475 max_samples_per_instance: crate::qos::policy::ResourceLimit::Unlimited,
1476 },
1477 ))
1478 .with_listener(crate::ReaderListener::new().with_sample_rejected(
1479 |_reader, _metadata| {
1480 TRIGGERED.lock().unwrap().sample_rejected += 1;
1481 },
1482 ))
1483 .build()
1484 .unwrap();
1485 let writer = crate::Writer::new(&topic).unwrap();
1486
1487 let sample = crate::tests::topic::Data {
1488 x: 1,
1489 y: 2,
1490 ..crate::tests::topic::Data::default()
1491 };
1492 writer.write(&sample).unwrap();
1493 writer
1494 .write(&crate::tests::topic::Data {
1495 x: 2,
1496 y: 3,
1497 ..crate::tests::topic::Data::default()
1498 })
1499 .unwrap();
1500
1501 let samples = reader.take().unwrap();
1502 assert_eq!(samples.len(), 1);
1503 assert_eq!(*samples[0], sample);
1504 }
1505
1506 {
1507 let reader = crate::Reader::builder(&topic)
1508 .with_listener(
1509 crate::ReaderListener::new()
1510 .with_data_available(|_reader| {
1511 TRIGGERED.lock().unwrap().data_available += 1;
1512 })
1513 .with_subscription_matched(|_reader, _matched| {
1514 TRIGGERED.lock().unwrap().subscription_matched += 1;
1515 })
1516 .with_liveliness_changed(|_reader, _changed| {
1517 TRIGGERED.lock().unwrap().liveliness_changed += 1;
1518 })
1519 .with_sample_lost(|_reader, _metadata| {
1520 TRIGGERED.lock().unwrap().sample_lost += 1;
1521 }),
1522 )
1523 .build()
1524 .unwrap();
1525 let writer = crate::Writer::new(&topic).unwrap();
1526
1527 let sample = crate::tests::topic::Data::default();
1528 writer.write(&sample).unwrap();
1529
1530 let key = sample.as_key();
1531 writer
1532 .unregister_instance_with_timestamp(
1533 &key,
1534 (std::time::SystemTime::now() - std::time::Duration::from_secs(1))
1535 .try_into()
1536 .unwrap(),
1537 )
1538 .unwrap();
1539
1540 let samples = reader.take().unwrap();
1541 assert_eq!(samples.len(), 1);
1542
1543 assert_eq!(*samples[0], sample);
1544
1545 assert_eq!(
1546 *TRIGGERED.lock().unwrap(),
1547 Triggered {
1548 requested_incompatible_qos: 1,
1549 requested_deadline_missed: true,
1550 sample_rejected: 1,
1551 data_available: 2,
1552 subscription_matched: 1,
1553 liveliness_changed: 1,
1554 sample_lost: 1,
1555 }
1556 );
1557 }
1558 }
1559
1560 #[test]
1561 fn test_writer_listener_callbacks() {
1562 #[derive(Debug, PartialEq)]
1563 struct Triggered {
1564 liveliness_lost: bool,
1565 offered_deadline_missed: bool,
1566 offered_incompatible_qos: u32,
1567 publication_matched: u32,
1568 }
1569
1570 static TRIGGERED: std::sync::Mutex<Triggered> = std::sync::Mutex::new(Triggered {
1571 liveliness_lost: false,
1572 offered_deadline_missed: false,
1573 offered_incompatible_qos: 0,
1574 publication_matched: 0,
1575 });
1576
1577 let domain_id = crate::tests::domain::unique_id();
1578 let topic_name = crate::tests::topic::unique_name();
1579 let domain = crate::Domain::new(domain_id).unwrap();
1580
1581 let participant = crate::Participant::new(&domain).unwrap();
1582 let topic =
1583 crate::Topic::<crate::tests::topic::Data>::new(&participant, &topic_name).unwrap();
1584
1585 {
1586 let _reader = crate::Reader::builder(&topic)
1587 .with_qos(
1588 &crate::QoS::new().with_durability(crate::qos::policy::Durability::Persistent),
1589 )
1590 .build()
1591 .unwrap();
1592 let _writer = crate::Writer::builder(&topic)
1593 .with_listener(crate::WriterListener::new().with_offered_incompatible_qos(
1594 |_writer, _metadata| {
1595 TRIGGERED.lock().unwrap().offered_incompatible_qos += 1;
1596 },
1597 ))
1598 .build()
1599 .unwrap();
1600 }
1601
1602 {
1603 let qos = crate::QoS::new().with_deadline(crate::qos::policy::Deadline {
1604 period: crate::Duration::from_nanos(1_000_000),
1605 });
1606 let writer = crate::Writer::builder(&topic)
1607 .with_qos(&qos)
1608 .with_listener(crate::WriterListener::new().with_offered_deadline_missed(
1609 |_writer, _metadata| {
1610 TRIGGERED.lock().unwrap().offered_deadline_missed |= true;
1611 },
1612 ))
1613 .build()
1614 .unwrap();
1615 let reader = crate::Reader::builder(&topic)
1616 .with_qos(&qos)
1617 .build()
1618 .unwrap();
1619
1620 let sample = crate::tests::topic::Data::default();
1621 writer.write(&sample).unwrap();
1622
1623 let samples = reader.take().unwrap();
1624 assert_eq!(samples.len(), 1);
1625 assert_eq!(*samples[0], sample);
1626
1627 while !TRIGGERED.lock().unwrap().offered_deadline_missed {
1628 std::thread::sleep(std::time::Duration::from_nanos(50));
1629 }
1630 }
1631
1632 {
1633 let writer = crate::Writer::builder(&topic)
1634 .with_listener(
1635 crate::WriterListener::new()
1636 .with_liveliness_lost(|_writer, _metadata| {
1637 TRIGGERED.lock().unwrap().liveliness_lost |= true;
1638 })
1639 .with_publication_matched(|_writer, _metadata| {
1640 TRIGGERED.lock().unwrap().publication_matched += 1;
1641 }),
1642 )
1643 .with_qos(&crate::QoS::new().with_liveliness(
1644 crate::qos::policy::Liveliness::ManualByParticipant {
1645 lease_duration: crate::Duration::from_nanos(1_000_000),
1646 },
1647 ))
1648 .build()
1649 .unwrap();
1650
1651 let reader = crate::Reader::new(&topic).unwrap();
1652
1653 let sample = crate::tests::topic::Data::default();
1654 writer.write(&sample).unwrap();
1655
1656 let key = sample.as_key();
1657 writer
1658 .unregister_instance_with_timestamp(
1659 &key,
1660 (std::time::SystemTime::now() - std::time::Duration::from_secs(1))
1661 .try_into()
1662 .unwrap(),
1663 )
1664 .unwrap();
1665
1666 let samples = reader.take().unwrap();
1667 assert_eq!(samples.len(), 1);
1668
1669 assert_eq!(*samples[0], sample);
1670
1671 while !TRIGGERED.lock().unwrap().liveliness_lost {
1672 std::thread::sleep(std::time::Duration::from_nanos(50));
1673 }
1674 }
1675
1676 assert_eq!(
1677 *TRIGGERED.lock().unwrap(),
1678 Triggered {
1679 liveliness_lost: true,
1680 offered_deadline_missed: true,
1681 offered_incompatible_qos: 1,
1682 publication_matched: 2,
1683 }
1684 );
1685 }
1686}