1extern crate alloc;
18
19use alloc::collections::BTreeMap;
20use alloc::vec::Vec;
21
22#[cfg(feature = "std")]
23use std::sync::{Mutex, OnceLock};
24
25use crate::error::{DdsError, Result};
26use crate::participant::{DomainId, DomainParticipant};
27use crate::qos::DomainParticipantQos;
28use crate::runtime::RuntimeConfig;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct DomainParticipantFactoryQos {
36 pub autoenable_created_entities: bool,
39}
40
41impl Default for DomainParticipantFactoryQos {
42 fn default() -> Self {
43 Self {
44 autoenable_created_entities: true,
45 }
46 }
47}
48
49#[cfg(feature = "std")]
51#[derive(Debug)]
52pub struct DomainParticipantFactory {
53 participants: Mutex<BTreeMap<DomainId, Vec<DomainParticipant>>>,
58 default_participant_qos: Mutex<DomainParticipantQos>,
61 factory_qos: Mutex<DomainParticipantFactoryQos>,
63}
64
65#[cfg(not(feature = "std"))]
66#[derive(Debug, Default)]
67pub struct DomainParticipantFactory {}
68
69#[cfg(feature = "std")]
70impl DomainParticipantFactory {
71 pub fn instance() -> &'static Self {
74 static INSTANCE: OnceLock<DomainParticipantFactory> = OnceLock::new();
75 INSTANCE.get_or_init(|| Self {
76 participants: Mutex::new(BTreeMap::new()),
77 default_participant_qos: Mutex::new(DomainParticipantQos::default()),
78 factory_qos: Mutex::new(DomainParticipantFactoryQos::default()),
79 })
80 }
81
82 fn track(&self, p: &DomainParticipant) {
83 if let Ok(mut reg) = self.participants.lock() {
84 reg.entry(p.domain_id()).or_default().push(p.clone());
85 }
86 }
87
88 pub fn create_participant(
95 &self,
96 domain_id: DomainId,
97 qos: DomainParticipantQos,
98 ) -> Result<DomainParticipant> {
99 let config = RuntimeConfig {
101 user_data: qos.user_data.value.clone(),
102 ..RuntimeConfig::default()
103 };
104 let p = DomainParticipant::new_with_runtime(domain_id, qos, config)?;
105 self.track(&p);
106 Ok(p)
107 }
108
109 pub fn create_participant_with_config(
115 &self,
116 domain_id: DomainId,
117 qos: DomainParticipantQos,
118 config: RuntimeConfig,
119 ) -> Result<DomainParticipant> {
120 let p = DomainParticipant::new_with_runtime(domain_id, qos, config)?;
121 self.track(&p);
122 Ok(p)
123 }
124
125 #[must_use]
129 pub fn create_participant_offline(
130 &self,
131 domain_id: DomainId,
132 qos: DomainParticipantQos,
133 ) -> DomainParticipant {
134 let p = DomainParticipant::new(domain_id, qos);
135 self.track(&p);
136 p
137 }
138
139 #[must_use]
144 pub fn lookup_participant(&self, domain_id: DomainId) -> Option<DomainParticipant> {
145 let reg = self.participants.lock().ok()?;
146 reg.get(&domain_id).and_then(|v| v.first().cloned())
147 }
148
149 pub fn delete_participant(&self, p: &DomainParticipant) -> Result<()> {
158 let mut reg = self
159 .participants
160 .lock()
161 .map_err(|_| DdsError::PreconditionNotMet {
162 reason: "factory participants poisoned",
163 })?;
164 let target_handle = p.instance_handle();
165 let mut found = false;
166 if let Some(vec) = reg.get_mut(&p.domain_id()) {
167 let before = vec.len();
168 vec.retain(|q| q.instance_handle() != target_handle);
169 found = vec.len() < before;
170 if vec.is_empty() {
171 reg.remove(&p.domain_id());
172 }
173 }
174 drop(reg);
175 if !found {
176 return Err(DdsError::PreconditionNotMet {
177 reason: "participant not registered with this factory",
178 });
179 }
180 p.delete_contained_entities()
181 }
182
183 pub fn set_default_participant_qos(&self, qos: DomainParticipantQos) -> Result<()> {
189 let mut current =
190 self.default_participant_qos
191 .lock()
192 .map_err(|_| DdsError::PreconditionNotMet {
193 reason: "default qos poisoned",
194 })?;
195 *current = qos;
196 Ok(())
197 }
198
199 #[must_use]
201 pub fn get_default_participant_qos(&self) -> DomainParticipantQos {
202 self.default_participant_qos
203 .lock()
204 .map(|q| q.clone())
205 .unwrap_or_default()
206 }
207
208 pub fn set_qos(&self, qos: DomainParticipantFactoryQos) -> Result<()> {
213 let mut current = self
214 .factory_qos
215 .lock()
216 .map_err(|_| DdsError::PreconditionNotMet {
217 reason: "factory qos poisoned",
218 })?;
219 *current = qos;
220 Ok(())
221 }
222
223 #[must_use]
225 pub fn get_qos(&self) -> DomainParticipantFactoryQos {
226 self.factory_qos.lock().map(|q| *q).unwrap_or_default()
227 }
228}
229
230#[cfg(test)]
231#[allow(clippy::expect_used, clippy::unwrap_used)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn factory_is_singleton() {
237 let a = DomainParticipantFactory::instance();
238 let b = DomainParticipantFactory::instance();
239 assert!(core::ptr::eq(a, b));
240 }
241
242 #[test]
243 fn factory_creates_participant_with_correct_domain_id() {
244 let p = DomainParticipantFactory::instance()
247 .create_participant_offline(7, DomainParticipantQos::default());
248 assert_eq!(p.domain_id(), 7);
249 }
250
251 #[test]
254 fn lookup_participant_finds_registered_offline_participant() {
255 let f = DomainParticipantFactory::instance();
256 let domain = 91;
259 let _p = f.create_participant_offline(domain, DomainParticipantQos::default());
260 let found = f.lookup_participant(domain);
261 assert!(found.is_some());
262 assert_eq!(found.unwrap().domain_id(), domain);
263 }
264
265 #[test]
266 fn lookup_participant_returns_none_for_unknown_domain() {
267 let f = DomainParticipantFactory::instance();
268 assert!(f.lookup_participant(60_001).is_none());
270 }
271
272 #[test]
275 fn delete_participant_removes_from_registry() {
276 let f = DomainParticipantFactory::instance();
277 let domain = 92;
278 let p = f.create_participant_offline(domain, DomainParticipantQos::default());
279 assert!(f.lookup_participant(domain).is_some());
280 f.delete_participant(&p).unwrap();
281 assert!(f.lookup_participant(domain).is_none());
282 }
283
284 #[test]
285 fn delete_participant_unknown_returns_precondition_not_met() {
286 let f = DomainParticipantFactory::instance();
287 let detached =
290 crate::participant::DomainParticipant::new(93, DomainParticipantQos::default());
291 let res = f.delete_participant(&detached);
292 assert!(matches!(
293 res,
294 Err(crate::error::DdsError::PreconditionNotMet { .. })
295 ));
296 }
297
298 #[test]
301 fn default_participant_qos_roundtrips() {
302 let f = DomainParticipantFactory::instance();
303 let mut new_qos = f.get_default_participant_qos();
304 new_qos = new_qos.clone();
308 f.set_default_participant_qos(new_qos.clone()).unwrap();
309 let got = f.get_default_participant_qos();
310 assert_eq!(format!("{got:?}"), format!("{new_qos:?}"));
312 }
313
314 #[test]
317 fn factory_qos_default_is_autoenable_true() {
318 let q = DomainParticipantFactoryQos::default();
320 assert!(q.autoenable_created_entities);
321 }
322
323 #[test]
324 fn factory_set_get_qos_roundtrip() {
325 let f = DomainParticipantFactory::instance();
326 let q = DomainParticipantFactoryQos {
327 autoenable_created_entities: false,
328 };
329 f.set_qos(q).unwrap();
330 let got = f.get_qos();
331 assert!(!got.autoenable_created_entities);
332 f.set_qos(DomainParticipantFactoryQos::default()).unwrap();
334 }
335}