Skip to main content

zerodds_dcps/
listener.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Listener-Hierarchie (DDS DCPS 1.4 §2.2.4.2 + §2.2.2.*.3 set_listener).
4//!
5//! Listener sind asynchrone Notification-Hooks, die der Middleware-
6//! Layer aufruft, sobald sich ein Communication-Status ändert. Pro
7//! Entity-Typ gibt es einen Listener-Trait mit einem Callback je
8//! relevantem Status:
9//!
10//! ```text
11//! DomainParticipantListener   (13 Callbacks — alle Bubble-Up-Targets)
12//! ├── PublisherListener       (4  Callbacks — Writer-bezogen)
13//! │   └── DataWriterListener  (4  Callbacks)
14//! ├── SubscriberListener      (8  Callbacks — Reader + on_data_on_readers)
15//! │   └── DataReaderListener  (7  Callbacks — Reader-spezifisch)
16//! └── TopicListener           (1  Callback — on_inconsistent_topic)
17//! ```
18//!
19//! ## Bubble-Up (Spec §2.2.4.2.3)
20//!
21//! Wenn auf der "kleinsten" Entity (z.B. DataReader) **kein** Listener
22//! gesetzt ist (oder der Listener das Status-Bit nicht in seiner Mask
23//! hat), bubblet das Event nach oben zur nächst-grösseren Entity:
24//! `Reader → Subscriber → Participant`. Analog
25//! `Writer → Publisher → Participant`. Topic-Events bubbeln direkt zum
26//! Participant. Die `bubble_up_consumed`-Helfer in
27//! [`listener_dispatch`](crate::listener_dispatch) kapseln diese
28//! Resolution.
29//!
30//! ## Object-Safety
31//!
32//! Alle 6 Traits sind **object-safe** (keine `Self`-Returns, keine
33//! Generics, keine assoziierten Typen). Damit der jeweilige Trait
34//! generisch über `T: DdsType` einsetzbar ist (wir haben `Topic<T>`,
35//! `DataWriter<T>`, `DataReader<T>`), übergeben wir den Entity-Handle
36//! als opaken [`InstanceHandle`] — analog zum DDS-DCPS-IDL-PSM, das
37//! Listener-Callbacks ebenfalls nur den Entity-*Handle* gibt
38//! (DCPS 1.4 §2.3.3 IDL).
39//!
40//! Wir speichern den Listener als
41//! `Box<dyn ListenerTrait + Send + Sync>` im Entity-State, damit er
42//! Cross-Thread sichtbar ist (Spec sagt nicht, dass Listener-Callbacks
43//! aus dem Application-Thread laufen müssen).
44//!
45//! Alle Methoden haben `&self` (nicht `&mut self`), weil der Listener
46//! im hot path geteilt wird; der Callback-Body muss interior mutability
47//! verwenden, falls er State führt.
48//!
49//! ## Default-Impls
50//!
51//! Jede Methode hat ein Empty-Body. Anwender überschreiben nur die
52//! Callbacks, die sie wirklich brauchen.
53
54extern crate alloc;
55
56use alloc::boxed::Box;
57
58use crate::entity::StatusMask;
59use crate::instance_handle::InstanceHandle;
60use crate::psm_constants::status as status_bits;
61use crate::status::{
62    InconsistentTopicStatus, LivelinessChangedStatus, LivelinessLostStatus,
63    OfferedDeadlineMissedStatus, OfferedIncompatibleQosStatus, PublicationMatchedStatus,
64    RequestedDeadlineMissedStatus, RequestedIncompatibleQosStatus, SampleLostStatus,
65    SampleRejectedStatus, SubscriptionMatchedStatus,
66};
67
68// ============================================================================
69// TopicListener (Spec §2.2.2.3.2)
70// ============================================================================
71
72/// `TopicListener` — Spec §2.2.2.3.2 + §2.2.4.2.5.
73///
74/// Genau ein Callback: `on_inconsistent_topic`. Der `topic`-Parameter
75/// wird als opaker [`InstanceHandle`] übergeben (Spec §2.3.3 IDL-PSM).
76pub trait TopicListener: Send + Sync {
77    /// Spec §2.2.4.2.5 — wird gerufen, wenn ein anderes Topic mit
78    /// gleichem Namen, aber unterschiedlichem Type-Definition entdeckt
79    /// wird.
80    fn on_inconsistent_topic(&self, _topic: InstanceHandle, _status: InconsistentTopicStatus) {}
81}
82
83// ============================================================================
84// DataWriterListener (Spec §2.2.2.4.4)
85// ============================================================================
86
87/// `DataWriterListener` — Spec §2.2.2.4.4 + §2.2.4.2.4.
88///
89/// 4 Callbacks: `on_offered_deadline_missed`, `on_offered_incompatible_qos`,
90/// `on_liveliness_lost`, `on_publication_matched`.
91pub trait DataWriterListener: Send + Sync {
92    /// Spec §2.2.4.2.4.1 — Writer hat das offered DEADLINE-Versprechen
93    /// nicht eingehalten.
94    fn on_offered_deadline_missed(
95        &self,
96        _writer: InstanceHandle,
97        _status: OfferedDeadlineMissedStatus,
98    ) {
99    }
100
101    /// Spec §2.2.4.2.4.2 — ein matched Reader hat inkompatible
102    /// requested-QoS.
103    fn on_offered_incompatible_qos(
104        &self,
105        _writer: InstanceHandle,
106        _status: OfferedIncompatibleQosStatus,
107    ) {
108    }
109
110    /// Spec §2.2.4.2.4.3 — Writer wurde aus Sicht der Reader als
111    /// not_alive deklariert.
112    fn on_liveliness_lost(&self, _writer: InstanceHandle, _status: LivelinessLostStatus) {}
113
114    /// Spec §2.2.4.2.4.4 — ein neuer kompatibler Reader matched (oder
115    /// einer ist verschwunden).
116    fn on_publication_matched(&self, _writer: InstanceHandle, _status: PublicationMatchedStatus) {}
117}
118
119// ============================================================================
120// PublisherListener (Spec §2.2.2.4.3)
121// ============================================================================
122
123/// `PublisherListener` — Spec §2.2.2.4.3.
124///
125/// Inheritance-Form (Spec): "is a listener of the writers contained
126/// within the publisher". Wir spiegeln die 4 DataWriterListener-Methoden
127/// 1:1, damit der Publisher als Bubble-Up-Target funktioniert.
128pub trait PublisherListener: Send + Sync {
129    /// Bubble-Up von [`DataWriterListener::on_offered_deadline_missed`].
130    fn on_offered_deadline_missed(
131        &self,
132        _writer: InstanceHandle,
133        _status: OfferedDeadlineMissedStatus,
134    ) {
135    }
136
137    /// Bubble-Up von [`DataWriterListener::on_offered_incompatible_qos`].
138    fn on_offered_incompatible_qos(
139        &self,
140        _writer: InstanceHandle,
141        _status: OfferedIncompatibleQosStatus,
142    ) {
143    }
144
145    /// Bubble-Up von [`DataWriterListener::on_liveliness_lost`].
146    fn on_liveliness_lost(&self, _writer: InstanceHandle, _status: LivelinessLostStatus) {}
147
148    /// Bubble-Up von [`DataWriterListener::on_publication_matched`].
149    fn on_publication_matched(&self, _writer: InstanceHandle, _status: PublicationMatchedStatus) {}
150}
151
152// ============================================================================
153// DataReaderListener (Spec §2.2.2.5.7)
154// ============================================================================
155
156/// `DataReaderListener` — Spec §2.2.2.5.7 + §2.2.4.2.6.
157///
158/// 7 Reader-spezifische Callbacks (das achte, `on_data_on_readers`,
159/// gehört zum [`SubscriberListener`]).
160pub trait DataReaderListener: Send + Sync {
161    /// Spec §2.2.4.2.6.1 — neue Daten sind zum Reader gekommen.
162    fn on_data_available(&self, _reader: InstanceHandle) {}
163
164    /// Spec §2.2.4.2.6.2 — ein Sample wurde nie empfangen
165    /// (z.B. überschrieben durch einen jüngeren).
166    fn on_sample_lost(&self, _reader: InstanceHandle, _status: SampleLostStatus) {}
167
168    /// Spec §2.2.4.2.6.3 — ein Sample wurde abgewiesen
169    /// (RESOURCE_LIMITS).
170    fn on_sample_rejected(&self, _reader: InstanceHandle, _status: SampleRejectedStatus) {}
171
172    /// Spec §2.2.4.2.6.4 — der Reader hat keine Sample innerhalb des
173    /// requested DEADLINE bekommen.
174    fn on_requested_deadline_missed(
175        &self,
176        _reader: InstanceHandle,
177        _status: RequestedDeadlineMissedStatus,
178    ) {
179    }
180
181    /// Spec §2.2.4.2.6.5 — ein matched Writer hat inkompatible
182    /// offered-QoS.
183    fn on_requested_incompatible_qos(
184        &self,
185        _reader: InstanceHandle,
186        _status: RequestedIncompatibleQosStatus,
187    ) {
188    }
189
190    /// Spec §2.2.4.2.6.6 — Liveliness-Status der matched Writer
191    /// hat sich geändert.
192    fn on_liveliness_changed(&self, _reader: InstanceHandle, _status: LivelinessChangedStatus) {}
193
194    /// Spec §2.2.4.2.6.7 — neuer kompatibler Writer matched (oder weg).
195    fn on_subscription_matched(&self, _reader: InstanceHandle, _status: SubscriptionMatchedStatus) {
196    }
197}
198
199// ============================================================================
200// SubscriberListener (Spec §2.2.2.5.6)
201// ============================================================================
202
203/// `SubscriberListener` — Spec §2.2.2.5.6 + §2.2.4.2.7.
204///
205/// Erbt alle 7 Reader-Callbacks + 1 zusätzlichen `on_data_on_readers`.
206pub trait SubscriberListener: Send + Sync {
207    /// Spec §2.2.4.2.7.1 — irgendein Reader des Subscribers hat neue
208    /// Daten (Subscriber-Level-Notification).
209    fn on_data_on_readers(&self, _subscriber: InstanceHandle) {}
210
211    /// Bubble-Up von [`DataReaderListener::on_data_available`].
212    fn on_data_available(&self, _reader: InstanceHandle) {}
213
214    /// Bubble-Up von [`DataReaderListener::on_sample_lost`].
215    fn on_sample_lost(&self, _reader: InstanceHandle, _status: SampleLostStatus) {}
216
217    /// Bubble-Up von [`DataReaderListener::on_sample_rejected`].
218    fn on_sample_rejected(&self, _reader: InstanceHandle, _status: SampleRejectedStatus) {}
219
220    /// Bubble-Up von [`DataReaderListener::on_requested_deadline_missed`].
221    fn on_requested_deadline_missed(
222        &self,
223        _reader: InstanceHandle,
224        _status: RequestedDeadlineMissedStatus,
225    ) {
226    }
227
228    /// Bubble-Up von [`DataReaderListener::on_requested_incompatible_qos`].
229    fn on_requested_incompatible_qos(
230        &self,
231        _reader: InstanceHandle,
232        _status: RequestedIncompatibleQosStatus,
233    ) {
234    }
235
236    /// Bubble-Up von [`DataReaderListener::on_liveliness_changed`].
237    fn on_liveliness_changed(&self, _reader: InstanceHandle, _status: LivelinessChangedStatus) {}
238
239    /// Bubble-Up von [`DataReaderListener::on_subscription_matched`].
240    fn on_subscription_matched(&self, _reader: InstanceHandle, _status: SubscriptionMatchedStatus) {
241    }
242}
243
244// ============================================================================
245// DomainParticipantListener (Spec §2.2.2.2.3)
246// ============================================================================
247
248/// `DomainParticipantListener` — Spec §2.2.2.2.3 + §2.2.4.2.8.
249///
250/// Vereinigt alle Status-Callbacks aller untergeordneten Entities, weil
251/// jedes Event spec-treu nach ganz oben bubblen kann, wenn auf der
252/// engeren Entity kein Listener installiert ist.
253///
254/// Die Spec listet **13 Callbacks** (Vereinigung aller Status-Hooks):
255/// - 1 Topic       (`on_inconsistent_topic`)
256/// - 4 Writer-     (`on_offered_*`, `on_liveliness_lost`, `on_publication_matched`)
257/// - 7 Reader-     (`on_data_available`, `on_sample_*`,
258///                  `on_requested_*`, `on_liveliness_changed`,
259///                  `on_subscription_matched`)
260/// - 1 Subscriber- (`on_data_on_readers`)
261pub trait DomainParticipantListener: Send + Sync {
262    // -------- Topic --------
263
264    /// Bubble-Up von [`TopicListener::on_inconsistent_topic`].
265    fn on_inconsistent_topic(&self, _topic: InstanceHandle, _status: InconsistentTopicStatus) {}
266
267    // -------- Writer-Seite --------
268
269    /// Bubble-Up von [`PublisherListener::on_offered_deadline_missed`].
270    fn on_offered_deadline_missed(
271        &self,
272        _writer: InstanceHandle,
273        _status: OfferedDeadlineMissedStatus,
274    ) {
275    }
276
277    /// Bubble-Up von [`PublisherListener::on_offered_incompatible_qos`].
278    fn on_offered_incompatible_qos(
279        &self,
280        _writer: InstanceHandle,
281        _status: OfferedIncompatibleQosStatus,
282    ) {
283    }
284
285    /// Bubble-Up von [`PublisherListener::on_liveliness_lost`].
286    fn on_liveliness_lost(&self, _writer: InstanceHandle, _status: LivelinessLostStatus) {}
287
288    /// Bubble-Up von [`PublisherListener::on_publication_matched`].
289    fn on_publication_matched(&self, _writer: InstanceHandle, _status: PublicationMatchedStatus) {}
290
291    // -------- Reader-Seite --------
292
293    /// Bubble-Up von [`SubscriberListener::on_data_on_readers`].
294    fn on_data_on_readers(&self, _subscriber: InstanceHandle) {}
295
296    /// Bubble-Up von [`SubscriberListener::on_data_available`].
297    fn on_data_available(&self, _reader: InstanceHandle) {}
298
299    /// Bubble-Up von [`SubscriberListener::on_sample_lost`].
300    fn on_sample_lost(&self, _reader: InstanceHandle, _status: SampleLostStatus) {}
301
302    /// Bubble-Up von [`SubscriberListener::on_sample_rejected`].
303    fn on_sample_rejected(&self, _reader: InstanceHandle, _status: SampleRejectedStatus) {}
304
305    /// Bubble-Up von [`SubscriberListener::on_requested_deadline_missed`].
306    fn on_requested_deadline_missed(
307        &self,
308        _reader: InstanceHandle,
309        _status: RequestedDeadlineMissedStatus,
310    ) {
311    }
312
313    /// Bubble-Up von [`SubscriberListener::on_requested_incompatible_qos`].
314    fn on_requested_incompatible_qos(
315        &self,
316        _reader: InstanceHandle,
317        _status: RequestedIncompatibleQosStatus,
318    ) {
319    }
320
321    /// Bubble-Up von [`SubscriberListener::on_liveliness_changed`].
322    fn on_liveliness_changed(&self, _reader: InstanceHandle, _status: LivelinessChangedStatus) {}
323
324    /// Bubble-Up von [`SubscriberListener::on_subscription_matched`].
325    fn on_subscription_matched(&self, _reader: InstanceHandle, _status: SubscriptionMatchedStatus) {
326    }
327}
328
329// ============================================================================
330// Boxed-Listener-Aliases (für Speicherung im Entity-State)
331// ============================================================================
332
333/// Heap-allokierter, threadsicherer Box-Wrapper für die 6
334/// Listener-Traits. So speichert die jeweilige Entity ihren Listener.
335pub type BoxedTopicListener = Box<dyn TopicListener>;
336/// Vgl. [`BoxedTopicListener`].
337pub type BoxedDataWriterListener = Box<dyn DataWriterListener>;
338/// Vgl. [`BoxedTopicListener`].
339pub type BoxedPublisherListener = Box<dyn PublisherListener>;
340/// Vgl. [`BoxedTopicListener`].
341pub type BoxedDataReaderListener = Box<dyn DataReaderListener>;
342/// Vgl. [`BoxedTopicListener`].
343pub type BoxedSubscriberListener = Box<dyn SubscriberListener>;
344/// Vgl. [`BoxedTopicListener`].
345pub type BoxedDomainParticipantListener = Box<dyn DomainParticipantListener>;
346
347/// Arc-Variante fuer per Slot speichern wir den Listener als
348/// `Arc<dyn ...>`, weil der Hot-Path den Listener kurz unter dem
349/// Slot-Mutex klont und dann ausserhalb des Locks ruft (Deadlock-
350/// Vermeidung). Box wuerde das nicht zulassen.
351pub type ArcTopicListener = alloc::sync::Arc<dyn TopicListener>;
352/// Vgl. [`ArcTopicListener`].
353pub type ArcDataWriterListener = alloc::sync::Arc<dyn DataWriterListener>;
354/// Vgl. [`ArcTopicListener`].
355pub type ArcPublisherListener = alloc::sync::Arc<dyn PublisherListener>;
356/// Vgl. [`ArcTopicListener`].
357pub type ArcDataReaderListener = alloc::sync::Arc<dyn DataReaderListener>;
358/// Vgl. [`ArcTopicListener`].
359pub type ArcSubscriberListener = alloc::sync::Arc<dyn SubscriberListener>;
360/// Vgl. [`ArcTopicListener`].
361pub type ArcDomainParticipantListener = alloc::sync::Arc<dyn DomainParticipantListener>;
362
363// ============================================================================
364// Bubble-Up-Helpers
365// ============================================================================
366
367/// True wenn `mask` das Bit für `status` setzt **und** der Listener
368/// nicht-`None` ist. Diese Kombi entscheidet, ob ein Event auf der
369/// aktuellen Stufe konsumiert wird (Spec §2.2.4.2.3).
370#[inline]
371#[must_use]
372pub fn listener_handles(listener_present: bool, mask: StatusMask, status_bit: u32) -> bool {
373    listener_present && (mask & status_bit) != 0
374}
375
376/// Vorab-Hilfsfunktion: liefert den Status-Bit-Wert zu einem
377/// Status-Namen. Nur in Tests + Doku-Beispielen verwendet — Hot-Path
378/// nutzt direkt die Konstanten in [`crate::psm_constants::status`].
379#[must_use]
380pub fn status_bit_for_inconsistent_topic() -> u32 {
381    status_bits::INCONSISTENT_TOPIC
382}
383
384#[cfg(test)]
385#[allow(clippy::expect_used, clippy::unwrap_used)]
386mod tests {
387    use super::*;
388    use core::sync::atomic::{AtomicU32, Ordering};
389
390    // -------- Object-Safety: alle 6 Traits müssen als `dyn` benutzbar sein --------
391
392    #[test]
393    fn topic_listener_is_object_safe() {
394        struct Counter(AtomicU32);
395        impl TopicListener for Counter {
396            fn on_inconsistent_topic(
397                &self,
398                _topic: InstanceHandle,
399                _status: InconsistentTopicStatus,
400            ) {
401                self.0.fetch_add(1, Ordering::Relaxed);
402            }
403        }
404        let _: BoxedTopicListener = Box::new(Counter(AtomicU32::new(0)));
405    }
406
407    #[test]
408    fn datawriter_listener_is_object_safe() {
409        struct L;
410        impl DataWriterListener for L {}
411        let _: BoxedDataWriterListener = Box::new(L);
412    }
413
414    #[test]
415    fn publisher_listener_is_object_safe() {
416        struct L;
417        impl PublisherListener for L {}
418        let _: BoxedPublisherListener = Box::new(L);
419    }
420
421    #[test]
422    fn datareader_listener_is_object_safe() {
423        struct L;
424        impl DataReaderListener for L {}
425        let _: BoxedDataReaderListener = Box::new(L);
426    }
427
428    #[test]
429    fn subscriber_listener_is_object_safe() {
430        struct L;
431        impl SubscriberListener for L {}
432        let _: BoxedSubscriberListener = Box::new(L);
433    }
434
435    #[test]
436    fn participant_listener_is_object_safe() {
437        struct L;
438        impl DomainParticipantListener for L {}
439        let _: BoxedDomainParticipantListener = Box::new(L);
440    }
441
442    // -------- Default-Impls dürfen leeren Body haben --------
443
444    #[test]
445    fn default_callbacks_do_not_panic() {
446        // Empty-Impl auf allen 6 Traits.
447        struct Noop;
448        impl TopicListener for Noop {}
449        impl DataWriterListener for Noop {}
450        impl PublisherListener for Noop {}
451        impl DataReaderListener for Noop {}
452        impl SubscriberListener for Noop {}
453        impl DomainParticipantListener for Noop {}
454        // Wir können sie zumindest konstruieren + boxen — der
455        // Aufruf braucht eine Entity (s. Tests in entity.rs).
456        let _: BoxedDomainParticipantListener = Box::new(Noop);
457    }
458
459    #[test]
460    fn listener_handles_respects_mask_and_presence() {
461        let bit = status_bit_for_inconsistent_topic();
462        assert!(listener_handles(true, bit, bit));
463        assert!(!listener_handles(false, bit, bit));
464        assert!(!listener_handles(true, 0, bit));
465        // Bit nicht in Mask.
466        assert!(!listener_handles(true, status_bits::SAMPLE_LOST, bit));
467    }
468
469    #[test]
470    fn status_bit_for_inconsistent_topic_matches_psm() {
471        assert_eq!(
472            status_bit_for_inconsistent_topic(),
473            status_bits::INCONSISTENT_TOPIC
474        );
475    }
476
477    #[test]
478    fn all_listener_traits_default_methods_invoke_safely() {
479        // Stresst die Default-Bodies aller 6 Listener-Traits.
480        // Da alle Default-Methoden Empty-Bodies haben, gehen wir
481        // einfach durch und rufen sie auf einer Noop-Instanz.
482        struct Noop;
483        impl TopicListener for Noop {}
484        impl DataWriterListener for Noop {}
485        impl PublisherListener for Noop {}
486        impl DataReaderListener for Noop {}
487        impl SubscriberListener for Noop {}
488        impl DomainParticipantListener for Noop {}
489
490        let h = InstanceHandle::from_raw(1);
491        let n = Noop;
492        TopicListener::on_inconsistent_topic(&n, h, InconsistentTopicStatus::default());
493
494        DataWriterListener::on_offered_deadline_missed(
495            &n,
496            h,
497            OfferedDeadlineMissedStatus::default(),
498        );
499        DataWriterListener::on_offered_incompatible_qos(
500            &n,
501            h,
502            OfferedIncompatibleQosStatus::default(),
503        );
504        DataWriterListener::on_liveliness_lost(&n, h, LivelinessLostStatus::default());
505        DataWriterListener::on_publication_matched(&n, h, PublicationMatchedStatus::default());
506
507        PublisherListener::on_offered_deadline_missed(
508            &n,
509            h,
510            OfferedDeadlineMissedStatus::default(),
511        );
512        PublisherListener::on_offered_incompatible_qos(
513            &n,
514            h,
515            OfferedIncompatibleQosStatus::default(),
516        );
517        PublisherListener::on_liveliness_lost(&n, h, LivelinessLostStatus::default());
518        PublisherListener::on_publication_matched(&n, h, PublicationMatchedStatus::default());
519
520        DataReaderListener::on_data_available(&n, h);
521        DataReaderListener::on_sample_lost(&n, h, SampleLostStatus::default());
522        DataReaderListener::on_sample_rejected(&n, h, SampleRejectedStatus::default());
523        DataReaderListener::on_requested_deadline_missed(
524            &n,
525            h,
526            RequestedDeadlineMissedStatus::default(),
527        );
528        DataReaderListener::on_requested_incompatible_qos(
529            &n,
530            h,
531            RequestedIncompatibleQosStatus::default(),
532        );
533        DataReaderListener::on_liveliness_changed(&n, h, LivelinessChangedStatus::default());
534        DataReaderListener::on_subscription_matched(&n, h, SubscriptionMatchedStatus::default());
535
536        SubscriberListener::on_data_on_readers(&n, h);
537        SubscriberListener::on_data_available(&n, h);
538        SubscriberListener::on_sample_lost(&n, h, SampleLostStatus::default());
539        SubscriberListener::on_sample_rejected(&n, h, SampleRejectedStatus::default());
540        SubscriberListener::on_requested_deadline_missed(
541            &n,
542            h,
543            RequestedDeadlineMissedStatus::default(),
544        );
545        SubscriberListener::on_requested_incompatible_qos(
546            &n,
547            h,
548            RequestedIncompatibleQosStatus::default(),
549        );
550        SubscriberListener::on_liveliness_changed(&n, h, LivelinessChangedStatus::default());
551        SubscriberListener::on_subscription_matched(&n, h, SubscriptionMatchedStatus::default());
552
553        DomainParticipantListener::on_inconsistent_topic(&n, h, InconsistentTopicStatus::default());
554        DomainParticipantListener::on_offered_deadline_missed(
555            &n,
556            h,
557            OfferedDeadlineMissedStatus::default(),
558        );
559        DomainParticipantListener::on_offered_incompatible_qos(
560            &n,
561            h,
562            OfferedIncompatibleQosStatus::default(),
563        );
564        DomainParticipantListener::on_liveliness_lost(&n, h, LivelinessLostStatus::default());
565        DomainParticipantListener::on_publication_matched(
566            &n,
567            h,
568            PublicationMatchedStatus::default(),
569        );
570        DomainParticipantListener::on_data_on_readers(&n, h);
571        DomainParticipantListener::on_data_available(&n, h);
572        DomainParticipantListener::on_sample_lost(&n, h, SampleLostStatus::default());
573        DomainParticipantListener::on_sample_rejected(&n, h, SampleRejectedStatus::default());
574        DomainParticipantListener::on_requested_deadline_missed(
575            &n,
576            h,
577            RequestedDeadlineMissedStatus::default(),
578        );
579        DomainParticipantListener::on_requested_incompatible_qos(
580            &n,
581            h,
582            RequestedIncompatibleQosStatus::default(),
583        );
584        DomainParticipantListener::on_liveliness_changed(&n, h, LivelinessChangedStatus::default());
585        DomainParticipantListener::on_subscription_matched(
586            &n,
587            h,
588            SubscriptionMatchedStatus::default(),
589        );
590    }
591
592    #[test]
593    fn datareader_listener_call_runs_default_methods() {
594        struct Counters {
595            avail: AtomicU32,
596            lost: AtomicU32,
597        }
598        impl DataReaderListener for Counters {
599            fn on_data_available(&self, _r: InstanceHandle) {
600                self.avail.fetch_add(1, Ordering::Relaxed);
601            }
602            fn on_sample_lost(&self, _r: InstanceHandle, _s: SampleLostStatus) {
603                self.lost.fetch_add(1, Ordering::Relaxed);
604            }
605        }
606        let c = Counters {
607            avail: AtomicU32::new(0),
608            lost: AtomicU32::new(0),
609        };
610        let h = InstanceHandle::from_raw(1);
611        c.on_data_available(h);
612        c.on_data_available(h);
613        c.on_sample_lost(h, SampleLostStatus::default());
614        // Methoden, die wir nicht ueberschrieben haben, sollten als
615        // Default-No-Op funktionieren.
616        c.on_subscription_matched(h, SubscriptionMatchedStatus::default());
617        assert_eq!(c.avail.load(Ordering::Relaxed), 2);
618        assert_eq!(c.lost.load(Ordering::Relaxed), 1);
619    }
620}