Skip to main content

cyclonedds/
entity.rs

1//! The base of the DDS entity hierarchy.
2//!
3//! Most DDS objects ([`Participant`](crate::Participant),
4//! [`Topic`](crate::Topic), [`Reader`](crate::Reader),
5//! [`Writer`](crate::Writer), and others) are entities. See the
6//! [implementors of `Entity`](Entity#implementors) for the full list. This
7//! module provides the [`Entity`] trait with the common methods available to
8//! all entities.
9
10use crate::internal::ffi;
11use crate::{Result, Status};
12
13/// A unique opaque handle identifying an instance.
14///
15/// For keyed topics this corresponds to a specific key value, but applications
16/// should treat it as an opaque DDS handle.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
18pub struct InstanceHandle {
19    pub(crate) inner: cyclonedds_sys::dds_instance_handle_t,
20}
21
22/// A raw entity ID for an entity.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
24pub struct EntityId {
25    pub(crate) inner: cyclonedds_sys::dds_entity_t,
26}
27
28mod private {
29    /// Private trait for sealing downstream implementation of the
30    /// [`Entity`](super::Entity) trait.
31    pub trait Sealed {}
32}
33
34/// Common interface implemented by all members of the DDS entity hierarchy.
35///
36/// - [`Participant`](crate::Participant): the root entity representing membership in a domain.
37///   - [`WaitSet`](crate::WaitSet): blocks until one or more attached conditions are triggered.
38///   - [`GuardCondition`](crate::GuardCondition): a manually triggered condition for use with a
39///     [`WaitSet`](crate::WaitSet).
40///   - [`Topic<T>`](crate::Topic): names and types a data channel for a specific payload type `T`.
41///   - [`Publisher`](crate::Publisher): groups [`Writers`](crate::Writer) and controls their shared
42///     [`QoS`](crate::QoS).
43///     - [`Writer<T>`](crate::Writer): publishes samples of type `T` to a [`Topic`](crate::Topic).
44///   - [`Subscriber`](crate::Subscriber): groups [`Readers`](crate::Reader) and controls their
45///     shared [`QoS`](crate::QoS).
46///     - [`Reader<T>`](crate::Reader): receives samples of type `T` from a [`Topic`](crate::Topic).
47///       - [`ReadCondition<T>`](crate::ReadCondition): filters [`Reader`](crate::Reader) samples by
48///         [`sample`](crate::state::sample), [`view`](crate::state::view), and
49///         [`instance`](crate::state::instance) state.
50///       - [`QueryCondition<T, F>`](crate::QueryCondition): filters [`Reader`](crate::Reader)
51///         samples by [`sample state`](crate::State) and a predicate.
52pub trait Entity: private::Sealed {
53    /// Returns the [`EntityId`] of this entity.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// use cyclonedds::entity::Entity;
59    /// use cyclonedds::{Reader, Topic, Writer};
60    ///
61    /// # #[derive(
62    /// #     cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
63    /// # )]
64    /// # struct Data {
65    /// #     x: i32,
66    /// # }
67    /// # let domain = cyclonedds::Domain::default();
68    /// # let participant = cyclonedds::Participant::new(&domain)?;
69    /// let topic = Topic::<Data>::new(&participant, "Example")?;
70    /// let reader = Reader::new(&topic)?;
71    /// let writer = Writer::new(&topic)?;
72    ///
73    /// // The reader and the writer have distinct IDs.
74    /// assert_ne!(reader.id(), writer.id());
75    ///
76    /// # Ok::<_, cyclonedds::Error>(())
77    /// ```
78    fn id(&self) -> EntityId;
79
80    /// Returns the [`InstanceHandle`] of this entity.
81    ///
82    /// # Errors
83    ///
84    /// Returns an [`Error`](crate::Error) specifying the reason if the instance
85    /// handle fails to be retrieved.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use cyclonedds::entity::Entity;
91    /// use cyclonedds::{Reader, Topic, Writer};
92    ///
93    /// # #[derive(
94    /// #     cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
95    /// # )]
96    /// # struct Data {
97    /// #     x: i32,
98    /// # }
99    /// # let domain = cyclonedds::Domain::default();
100    /// # let participant = cyclonedds::Participant::new(&domain)?;
101    /// let topic = Topic::<Data>::new(&participant, "Example")?;
102    /// let reader = Reader::new(&topic)?;
103    /// let writer = Writer::new(&topic)?;
104    ///
105    /// // The reader and the writer have distinct instance handles.
106    /// assert_ne!(reader.instance_handle()?, writer.instance_handle()?);
107    ///
108    /// // Instance handles can be used to identify entities across various API
109    /// // calls. For example, the writer's handle appears in the set of matched
110    /// // publications.
111    /// let matched = reader.matched_publications()?;
112    /// assert_eq!(matched[0], writer.instance_handle()?);
113    /// # Ok::<_, cyclonedds::Error>(())
114    /// ```
115    fn instance_handle(&self) -> Result<InstanceHandle> {
116        let entity = self.id();
117        let inner = ffi::dds_get_instance_handle(entity.inner)?;
118        Ok(InstanceHandle { inner })
119    }
120
121    /// Returns the set of status flags that have changed since they were last
122    /// [`read`](crate::Reader::read) or [`taken`](crate::Reader::take).
123    ///
124    /// # Errors
125    ///
126    /// - Returns an [`Error`](crate::Error) if the status bits of the corresponding entity could
127    ///   not be retrieved (e.g. the entity no longer exists).
128    ///
129    /// - Returns [`BadParameter`](crate::Error::BadParameter) if the retrieved bits do not
130    ///   correspond to a valid [`Status`].
131    ///
132    /// # Examples
133    ///
134    /// ```
135    /// use cyclonedds::entity::Entity;
136    /// use cyclonedds::{Reader, Status, Topic, Writer};
137    ///
138    /// # #[derive(
139    /// #     cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
140    /// # )]
141    /// # struct Data {
142    /// #     x: i32,
143    /// # }
144    /// # let domain = cyclonedds::Domain::default();
145    /// # let participant = cyclonedds::Participant::new(&domain)?;
146    /// let topic = Topic::<Data>::new(&participant, "Example")?;
147    /// let reader = Reader::new(&topic)?;
148    ///
149    /// // The reader has been created but nothing in particular has happened in
150    /// // terms of status changes.
151    /// let changed = reader.status_changes()?;
152    /// assert_eq!(changed, Status::empty());
153    ///
154    /// // The writer that is created will match with the reader.
155    /// let writer = Writer::new(&topic)?;
156    ///
157    /// // After a writer matches, the reader reports a status change.
158    /// let changed = reader.status_changes()?;
159    /// assert!(changed.contains(Status::SubscriptionMatched));
160    /// # Ok::<_, cyclonedds::Error>(())
161    /// ```
162    fn status_changes(&self) -> Result<Status> {
163        let entity = self.id();
164        let status = ffi::dds_get_status_changes(entity.inner)?;
165        Status::from_bits(status).ok_or(crate::error::Error::BadParameter)
166    }
167
168    /// Takes and clears the status flags matching `mask`, or all flags if
169    /// `mask` is `None`.
170    ///
171    /// Unlike [`read_status`](Entity::read_status), this clears the returned
172    /// flags on the entity.
173    ///
174    /// # Errors
175    ///
176    /// - Returns an [`Error`](crate::Error) if the status bits of the corresponding entity could
177    ///   not be retrieved (e.g. the entity no longer exists or the status mask contains entries
178    ///   that do not apply to the entity type).
179    ///
180    /// - Returns [`BadParameter`](crate::Error::BadParameter) if the retrieved bits do not
181    ///   correspond to a valid [`Status`].
182    ///
183    /// # Examples
184    ///
185    /// ```
186    /// use cyclonedds::entity::Entity;
187    /// use cyclonedds::{Reader, Status, Topic, Writer};
188    ///
189    /// # #[derive(
190    /// #     cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
191    /// # )]
192    /// # struct Data {
193    /// #     x: i32,
194    /// # }
195    /// # let domain = cyclonedds::Domain::default();
196    /// # let participant = cyclonedds::Participant::new(&domain)?;
197    /// let topic = Topic::<Data>::new(&participant, "Example")?;
198    /// let reader = Reader::new(&topic)?;
199    /// let writer = Writer::new(&topic)?;
200    ///
201    /// // The reader has matched with the writer, so its status should have
202    /// // updated.
203    /// let status = reader.take_status(Some(Status::SubscriptionMatched))?;
204    /// assert!(status.contains(Status::SubscriptionMatched));
205    ///
206    /// // The flag has been cleared; a second take returns empty.
207    /// let cleared = reader.take_status(Some(Status::SubscriptionMatched))?;
208    /// assert!(cleared.is_empty());
209    /// # Ok::<_, cyclonedds::Error>(())
210    /// ```
211    fn take_status(&self, mask: Option<Status>) -> Result<Status> {
212        let entity = self.id();
213        let mask = mask.unwrap_or(Status::all()).bits();
214        let status = ffi::dds_take_status(entity.inner, mask)?;
215        Status::from_bits(status).ok_or(crate::error::Error::BadParameter)
216    }
217
218    /// Reads the status flags matching `mask` without clearing them, or all
219    /// flags if `mask` is `None`.
220    ///
221    /// # Errors
222    ///
223    /// - Returns an [`Error`](crate::Error) if the status bits of the corresponding entity could
224    ///   not be retrieved (e.g. the entity no longer exists).
225    ///
226    /// - Returns [`BadParameter`](crate::Error::BadParameter) if the retrieved bits do not
227    ///   correspond to a valid [`Status`].
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// use cyclonedds::entity::Entity;
233    /// use cyclonedds::{Reader, Status, Topic, Writer};
234    ///
235    /// # #[derive(
236    /// #     cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
237    /// # )]
238    /// # struct Data {
239    /// #     x: i32,
240    /// # }
241    /// # let domain = cyclonedds::Domain::default();
242    /// # let participant = cyclonedds::Participant::new(&domain)?;
243    /// let topic = Topic::<Data>::new(&participant, "Example")?;
244    /// let reader = Reader::new(&topic)?;
245    /// let writer = Writer::new(&topic)?;
246    ///
247    /// // The reader has matched with the writer, so its status should have
248    /// // updated.
249    /// let status = reader.read_status(Some(Status::SubscriptionMatched))?;
250    /// assert!(status.contains(Status::SubscriptionMatched));
251    ///
252    /// // The flag is preserved; a second read returns the same value.
253    /// let same = reader.read_status(Some(Status::SubscriptionMatched))?;
254    /// assert_eq!(status, same);
255    /// # Ok::<_, cyclonedds::Error>(())
256    /// ```
257    fn read_status(&self, mask: Option<Status>) -> Result<Status> {
258        let entity = self.id();
259        let mask = mask.unwrap_or(Status::all()).bits();
260        let status = ffi::dds_read_status(entity.inner, mask)?;
261        Status::from_bits(status).ok_or(crate::error::Error::BadParameter)
262    }
263
264    /// Returns the status mask enabled on the entity.
265    ///
266    /// # Errors
267    ///
268    /// - Returns an [`Error`](crate::Error) if the status mask of the corresponding entity could
269    ///   not be retrieved (e.g. the entity no longer exists).
270    ///
271    /// - Returns [`BadParameter`](crate::Error::BadParameter) if the retrieved bits do not
272    ///   correspond to a valid [`Status`].
273    ///
274    /// # Examples
275    /// ```
276    /// use cyclonedds::entity::Entity;
277    /// use cyclonedds::{Status, Topic, Writer};
278    ///
279    /// # #[derive(
280    /// #     cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
281    /// # )]
282    /// # struct Data {
283    /// #     x: i32,
284    /// # }
285    /// # let domain = cyclonedds::Domain::default();
286    /// # let participant = cyclonedds::Participant::new(&domain)?;
287    /// let topic = Topic::<Data>::new(&participant, "Example")?;
288    /// let writer = Writer::new(&topic)?;
289    ///
290    /// // Get the initial active status mask.
291    /// assert_eq!(
292    ///     writer.status_mask()?,
293    ///     Status::OfferedDeadlineMissed
294    ///         | Status::OfferedIncompatibleQoS
295    ///         | Status::LivelinessLost
296    ///         | Status::PublicationMatched
297    /// );
298    /// # Ok::<_, cyclonedds::Error>(())
299    /// ```
300    fn status_mask(&self) -> Result<Status> {
301        let entity = self.id();
302        let mask = ffi::dds_get_status_mask(entity.inner)?;
303        Status::from_bits(mask).ok_or(crate::error::Error::BadParameter)
304    }
305
306    /// Sets and enables a status mask on the entity.
307    ///
308    /// Only status flags included in `mask` will trigger listener callbacks or
309    /// be reported via [`status_changes`](Entity::status_changes).
310    ///
311    /// # Errors
312    ///
313    /// - Returns an [`Error`](crate::Error) if the status mask of the corresponding entity could
314    ///   not be set (e.g. the entity no longer exists).
315    ///
316    /// # Examples
317    /// ```
318    /// use cyclonedds::entity::Entity;
319    /// use cyclonedds::{Status, Topic, Writer};
320    ///
321    /// # #[derive(
322    /// #     cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Clone, Debug, Default,
323    /// # )]
324    /// # struct Data {
325    /// #     x: i32,
326    /// # }
327    /// # let domain = cyclonedds::Domain::default();
328    /// # let participant = cyclonedds::Participant::new(&domain)?;
329    /// let topic = Topic::<Data>::new(&participant, "Example")?;
330    /// let writer = Writer::new(&topic)?;
331    ///
332    /// // Set the active status mask.
333    /// writer.set_status_mask(Status::PublicationMatched)?;
334    /// // Get the active status mask.
335    /// assert_eq!(writer.status_mask()?, Status::PublicationMatched);
336    /// # Ok::<_, cyclonedds::Error>(())
337    /// ```
338    fn set_status_mask(&self, mask: Status) -> Result<()> {
339        let entity = self.id();
340        let mask = mask.bits();
341        ffi::dds_set_status_mask(entity.inner, mask)
342    }
343}
344
345macro_rules! impl_entity {
346    ($ty:ty) => {
347        impl private::Sealed for $ty {}
348
349        impl Entity for $ty {
350            fn id(&self) -> EntityId {
351                EntityId { inner: self.inner }
352            }
353        }
354    };
355    ($ty:ty where $($bounds:tt)*) => {
356        impl<$($bounds)*> private::Sealed for $ty {}
357
358        impl<$($bounds)*> Entity for $ty {
359            fn id(&self) -> EntityId {
360                EntityId { inner: self.inner }
361            }
362        }
363    };
364}
365
366impl_entity!(crate::Participant<'_>);
367impl_entity!(crate::Topic<'_, '_, T> where T: crate::Topicable);
368impl_entity!(crate::Publisher<'_, '_>);
369impl_entity!(crate::Subscriber<'_, '_>);
370impl_entity!(crate::Reader<'_, '_, '_, T> where T: crate::Topicable);
371impl_entity!(crate::Writer<'_, '_, '_, T> where T: crate::Topicable);
372impl_entity!(crate::ReadCondition<'_, '_, '_, '_, T> where T: crate::Topicable);
373impl_entity!(crate::QueryCondition<'_, '_, '_, '_, T, F> where T: crate::Topicable, F: Fn(&T) -> bool);
374impl_entity!(crate::GuardCondition<'_>);
375impl_entity!(crate::WaitSet<'_, '_, '_, A> where A);
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380
381    #[test]
382    fn test_entity_id_all_entity_types() {
383        let domain_id = crate::tests::domain::unique_id();
384        let domain = crate::Domain::new(domain_id).unwrap();
385        let participant = crate::Participant::new(&domain).unwrap();
386        let topic_name = crate::tests::topic::unique_name();
387        let topic =
388            crate::Topic::<crate::tests::topic::Data>::new(&participant, &topic_name).unwrap();
389        let publisher = crate::Publisher::new(&participant).unwrap();
390        let subscriber = crate::Subscriber::new(&participant).unwrap();
391        let reader = crate::Reader::new(&topic).unwrap();
392        let writer = crate::Writer::new(&topic).unwrap();
393        let read_condition = crate::ReadCondition::new(&reader, crate::state::sample::Any).unwrap();
394        let query_condition =
395            crate::QueryCondition::new(&reader, crate::State::empty(), |_| true).unwrap();
396        let guard_condition = crate::GuardCondition::new(&participant).unwrap();
397        let waitset = crate::WaitSet::<()>::new(&participant).unwrap();
398
399        assert_eq!(participant.id().inner, participant.inner);
400        assert_eq!(topic.id().inner, topic.inner);
401        assert_eq!(publisher.id().inner, publisher.inner);
402        assert_eq!(subscriber.id().inner, subscriber.inner);
403        assert_eq!(reader.id().inner, reader.inner);
404        assert_eq!(writer.id().inner, writer.inner);
405        assert_eq!(read_condition.id().inner, read_condition.inner);
406        assert_eq!(query_condition.id().inner, query_condition.inner);
407        assert_eq!(guard_condition.id().inner, guard_condition.inner);
408        assert_eq!(waitset.id().inner, waitset.inner);
409    }
410
411    #[test]
412    fn test_entity_methods_on_invalid_participant() {
413        let domain_id = crate::tests::domain::unique_id();
414        let domain = crate::Domain::new(domain_id).unwrap();
415        let mut participant = crate::Participant::new(&domain).unwrap();
416        let participant_id = participant.inner;
417        participant.inner = 0;
418
419        assert_eq!(
420            crate::Error::BadParameter,
421            participant.instance_handle().unwrap_err()
422        );
423        assert_eq!(
424            crate::Error::BadParameter,
425            participant.status_changes().unwrap_err()
426        );
427        assert_eq!(
428            crate::Error::BadParameter,
429            participant.take_status(None).unwrap_err()
430        );
431        assert_eq!(
432            crate::Error::BadParameter,
433            participant.read_status(None).unwrap_err()
434        );
435        assert_eq!(
436            crate::Error::BadParameter,
437            participant.status_mask().unwrap_err()
438        );
439        assert_eq!(
440            crate::Error::BadParameter,
441            participant
442                .set_status_mask(crate::Status::InconsistentTopic)
443                .unwrap_err()
444        );
445
446        participant.inner = participant_id;
447    }
448
449    #[test]
450    fn test_entity_methods_on_participant() {
451        let domain_id = crate::tests::domain::unique_id();
452        let domain = crate::Domain::new(domain_id).unwrap();
453        let participant = crate::Participant::new(&domain).unwrap();
454
455        let result = participant.instance_handle();
456        assert!(result.is_ok());
457        let status_changes = participant.status_changes().unwrap();
458        assert!(status_changes.is_empty());
459        let result = participant.set_status_mask(crate::Status::empty());
460        assert!(result.is_ok());
461        let mask = participant.status_mask().unwrap();
462        assert_eq!(mask, crate::Status::empty());
463        let status = participant
464            .read_status(Some(crate::Status::empty()))
465            .unwrap();
466        assert!(status.is_empty());
467        let status = participant
468            .take_status(Some(crate::Status::empty()))
469            .unwrap();
470        assert!(status.is_empty());
471    }
472
473    #[test]
474    fn test_entity_methods_on_reader() {
475        let domain_id = crate::tests::domain::unique_id();
476        let domain = crate::Domain::new(domain_id).unwrap();
477        let topic_name = crate::tests::topic::unique_name();
478        let participant = crate::Participant::new(&domain).unwrap();
479        let topic =
480            crate::Topic::<crate::tests::topic::Data>::new(&participant, &topic_name).unwrap();
481        let reader = crate::Reader::new(&topic).unwrap();
482
483        let result = reader.instance_handle();
484        assert!(result.is_ok());
485        let status_changes = reader.status_changes().unwrap();
486        assert!(status_changes.is_empty());
487        let result = reader.set_status_mask(crate::Status::SubscriptionMatched);
488        assert!(result.is_ok());
489        let mask = reader.status_mask().unwrap();
490        assert_eq!(mask, crate::Status::SubscriptionMatched);
491        let status = reader
492            .read_status(Some(crate::Status::SubscriptionMatched))
493            .unwrap();
494        assert!(status.is_empty());
495        let status = reader
496            .take_status(Some(crate::Status::SubscriptionMatched))
497            .unwrap();
498        assert!(status.is_empty());
499    }
500}