1extern crate alloc;
28use alloc::collections::BTreeMap;
29
30use crate::error::XrceError;
31use crate::object_id::ObjectId;
32use crate::object_kind::ObjectKind;
33use crate::object_repr::ObjectVariant;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
37pub struct CreationMode {
38 pub reuse: bool,
40 pub replace: bool,
42}
43
44impl CreationMode {
45 pub const STRICT: Self = Self {
47 reuse: false,
48 replace: false,
49 };
50 pub const REUSE: Self = Self {
52 reuse: true,
53 replace: false,
54 };
55 pub const REPLACE: Self = Self {
57 reuse: false,
58 replace: true,
59 };
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct ObjectInstance {
65 pub kind: ObjectKind,
67 pub variant: ObjectVariant,
69 pub version: u32,
71}
72
73impl ObjectInstance {
74 #[must_use]
76 pub fn new(kind: ObjectKind, variant: ObjectVariant) -> Self {
77 Self {
78 kind,
79 variant,
80 version: 0,
81 }
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum CreateOutcome {
88 Created,
90 Reused {
94 equal: bool,
96 },
97 Replaced,
99 Conflict,
101}
102
103#[derive(Debug, Clone, Default)]
105pub struct ObjectStore {
106 objects: BTreeMap<ObjectId, ObjectInstance>,
107}
108
109impl ObjectStore {
110 #[must_use]
112 pub fn new() -> Self {
113 Self::default()
114 }
115
116 #[must_use]
118 pub fn len(&self) -> usize {
119 self.objects.len()
120 }
121
122 #[must_use]
124 pub fn is_empty(&self) -> bool {
125 self.objects.is_empty()
126 }
127
128 #[must_use]
130 pub fn contains(&self, id: ObjectId) -> bool {
131 self.objects.contains_key(&id)
132 }
133
134 #[must_use]
136 pub fn get(&self, id: ObjectId) -> Option<&ObjectInstance> {
137 self.objects.get(&id)
138 }
139
140 pub fn iter(&self) -> impl Iterator<Item = (ObjectId, &ObjectInstance)> {
142 self.objects.iter().map(|(&id, inst)| (id, inst))
143 }
144
145 pub fn create(
154 &mut self,
155 id: ObjectId,
156 kind: ObjectKind,
157 variant: ObjectVariant,
158 mode: CreationMode,
159 ) -> Result<CreateOutcome, XrceError> {
160 if id.is_invalid() {
161 return Err(XrceError::ValueOutOfRange {
162 message: "cannot create OBJECTID_INVALID",
163 });
164 }
165 let id_kind = id.kind()?;
166 if id_kind != kind {
167 return Err(XrceError::ValueOutOfRange {
168 message: "ObjectId.kind() does not match argument kind",
169 });
170 }
171
172 let existing = self.objects.get(&id);
173 match (existing, mode.reuse, mode.replace) {
174 (None, _, _) => {
175 self.objects.insert(id, ObjectInstance::new(kind, variant));
176 Ok(CreateOutcome::Created)
177 }
178 (Some(prev), true, _) => {
179 let equal = prev.kind == kind && prev.variant == variant;
180 Ok(CreateOutcome::Reused { equal })
181 }
182 (Some(prev), false, true) => {
183 let next_version = prev.version.wrapping_add(1);
184 self.objects.insert(
185 id,
186 ObjectInstance {
187 kind,
188 variant,
189 version: next_version,
190 },
191 );
192 Ok(CreateOutcome::Replaced)
193 }
194 (Some(_), false, false) => Ok(CreateOutcome::Conflict),
195 }
196 }
197
198 pub fn delete(&mut self, id: ObjectId) -> bool {
200 self.objects.remove(&id).is_some()
201 }
202
203 pub fn clear(&mut self) {
205 self.objects.clear();
206 }
207
208 pub fn iter_by_kind(
210 &self,
211 kind: ObjectKind,
212 ) -> impl Iterator<Item = (ObjectId, &ObjectInstance)> {
213 self.iter().filter(move |(_, inst)| inst.kind == kind)
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 #![allow(clippy::expect_used, clippy::unwrap_used)]
220 use super::*;
221 use alloc::string::String;
222
223 fn participant_xml(name: &str) -> ObjectVariant {
224 ObjectVariant::ByXmlString(alloc::format!(
225 "<dds><domain_participant><name>{name}</name></domain_participant></dds>"
226 ))
227 }
228
229 #[test]
230 fn create_inserts_new_object() {
231 let mut store = ObjectStore::new();
232 let id = ObjectId::new(0x010, ObjectKind::Participant).unwrap();
233 let r = store
234 .create(
235 id,
236 ObjectKind::Participant,
237 participant_xml("p1"),
238 CreationMode::STRICT,
239 )
240 .unwrap();
241 assert_eq!(r, CreateOutcome::Created);
242 assert!(store.contains(id));
243 assert_eq!(store.len(), 1);
244 }
245
246 #[test]
247 fn create_strict_on_existing_returns_conflict() {
248 let mut store = ObjectStore::new();
249 let id = ObjectId::new(0x010, ObjectKind::Topic).unwrap();
250 store
251 .create(
252 id,
253 ObjectKind::Topic,
254 ObjectVariant::ByReference("foo".into()),
255 CreationMode::STRICT,
256 )
257 .unwrap();
258 let r = store
259 .create(
260 id,
261 ObjectKind::Topic,
262 ObjectVariant::ByReference("bar".into()),
263 CreationMode::STRICT,
264 )
265 .unwrap();
266 assert_eq!(r, CreateOutcome::Conflict);
267 }
268
269 #[test]
270 fn create_reuse_returns_equal_marker() {
271 let mut store = ObjectStore::new();
272 let id = ObjectId::new(0x011, ObjectKind::Topic).unwrap();
273 let first = ObjectVariant::ByReference("the_topic".into());
274 store
275 .create(id, ObjectKind::Topic, first.clone(), CreationMode::STRICT)
276 .unwrap();
277 let r1 = store
279 .create(id, ObjectKind::Topic, first.clone(), CreationMode::REUSE)
280 .unwrap();
281 assert_eq!(r1, CreateOutcome::Reused { equal: true });
282 let r2 = store
284 .create(
285 id,
286 ObjectKind::Topic,
287 ObjectVariant::ByReference("other".into()),
288 CreationMode::REUSE,
289 )
290 .unwrap();
291 assert_eq!(r2, CreateOutcome::Reused { equal: false });
292 assert_eq!(store.get(id).unwrap().variant, first);
294 assert_eq!(store.get(id).unwrap().version, 0);
295 }
296
297 #[test]
298 fn create_replace_increments_version() {
299 let mut store = ObjectStore::new();
300 let id = ObjectId::new(0x012, ObjectKind::DataWriter).unwrap();
301 store
302 .create(
303 id,
304 ObjectKind::DataWriter,
305 ObjectVariant::ByReference("wr1".into()),
306 CreationMode::STRICT,
307 )
308 .unwrap();
309 let r = store
310 .create(
311 id,
312 ObjectKind::DataWriter,
313 ObjectVariant::ByReference("wr2".into()),
314 CreationMode::REPLACE,
315 )
316 .unwrap();
317 assert_eq!(r, CreateOutcome::Replaced);
318 let inst = store.get(id).unwrap();
319 assert_eq!(inst.version, 1);
320 assert_eq!(inst.variant, ObjectVariant::ByReference("wr2".into()));
321 }
322
323 #[test]
324 fn delete_removes_object() {
325 let mut store = ObjectStore::new();
326 let id = ObjectId::new(0x020, ObjectKind::Subscriber).unwrap();
327 store
328 .create(
329 id,
330 ObjectKind::Subscriber,
331 ObjectVariant::ByReference("s".into()),
332 CreationMode::STRICT,
333 )
334 .unwrap();
335 assert!(store.delete(id));
336 assert!(!store.contains(id));
337 assert!(!store.delete(id));
338 }
339
340 #[test]
341 fn create_kind_mismatch_with_id_rejected() {
342 let mut store = ObjectStore::new();
343 let id = ObjectId::new(0x030, ObjectKind::Topic).unwrap();
345 let r = store.create(
346 id,
347 ObjectKind::DataWriter,
348 ObjectVariant::ByReference("x".into()),
349 CreationMode::STRICT,
350 );
351 assert!(r.is_err());
352 }
353
354 #[test]
355 fn create_invalid_object_id_rejected() {
356 let mut store = ObjectStore::new();
357 let r = store.create(
358 crate::object_id::OBJECTID_INVALID,
359 ObjectKind::Topic,
360 ObjectVariant::ByReference("x".into()),
361 CreationMode::STRICT,
362 );
363 assert!(r.is_err());
364 }
365
366 #[test]
367 fn iter_by_kind_filters() {
368 let mut store = ObjectStore::new();
369 let topic = ObjectId::new(0x100, ObjectKind::Topic).unwrap();
370 let dw = ObjectId::new(0x101, ObjectKind::DataWriter).unwrap();
371 let dr = ObjectId::new(0x102, ObjectKind::DataReader).unwrap();
372 store
373 .create(
374 topic,
375 ObjectKind::Topic,
376 ObjectVariant::ByReference("t".into()),
377 CreationMode::STRICT,
378 )
379 .unwrap();
380 store
381 .create(
382 dw,
383 ObjectKind::DataWriter,
384 ObjectVariant::ByReference("w".into()),
385 CreationMode::STRICT,
386 )
387 .unwrap();
388 store
389 .create(
390 dr,
391 ObjectKind::DataReader,
392 ObjectVariant::ByReference("r".into()),
393 CreationMode::STRICT,
394 )
395 .unwrap();
396 let topics: alloc::vec::Vec<_> = store.iter_by_kind(ObjectKind::Topic).collect();
397 assert_eq!(topics.len(), 1);
398 assert_eq!(topics[0].0, topic);
399 }
400
401 #[test]
402 fn clear_drops_everything() {
403 let mut store = ObjectStore::new();
404 for i in 0..10 {
405 let id = ObjectId::new(i, ObjectKind::Topic).unwrap();
406 store
407 .create(
408 id,
409 ObjectKind::Topic,
410 ObjectVariant::ByReference(String::from("x")),
411 CreationMode::STRICT,
412 )
413 .unwrap();
414 }
415 assert_eq!(store.len(), 10);
416 store.clear();
417 assert!(store.is_empty());
418 }
419
420 #[test]
421 fn replace_then_reuse_is_consistent() {
422 let mut store = ObjectStore::new();
423 let id = ObjectId::new(0x040, ObjectKind::Topic).unwrap();
424 let v1 = ObjectVariant::ByReference("A".into());
425 let v2 = ObjectVariant::ByReference("B".into());
426 store
427 .create(id, ObjectKind::Topic, v1.clone(), CreationMode::STRICT)
428 .unwrap();
429 store
430 .create(id, ObjectKind::Topic, v2.clone(), CreationMode::REPLACE)
431 .unwrap();
432 let r = store
433 .create(id, ObjectKind::Topic, v2.clone(), CreationMode::REUSE)
434 .unwrap();
435 assert_eq!(r, CreateOutcome::Reused { equal: true });
436 assert_eq!(store.get(id).unwrap().version, 1);
437 }
438
439 #[test]
440 fn iter_yields_sorted_ids() {
441 let mut store = ObjectStore::new();
442 for raw in [0x300u16, 0x100, 0x200] {
443 let id = ObjectId::new(raw, ObjectKind::Topic).unwrap();
444 store
445 .create(
446 id,
447 ObjectKind::Topic,
448 ObjectVariant::ByReference(String::from("x")),
449 CreationMode::STRICT,
450 )
451 .unwrap();
452 }
453 let ids: alloc::vec::Vec<ObjectId> = store.iter().map(|(id, _)| id).collect();
454 assert!(ids.windows(2).all(|w| w[0] <= w[1]));
456 }
457}