1use dashmap::DashMap;
2use std::any::TypeId;
3use std::hash::Hash;
4use std::sync::Arc;
5
6#[derive(Clone)]
28pub struct CodexMap {
29 inner: Arc<DashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
30}
31
32impl Default for CodexMap {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl CodexMap {
39 #[inline]
41 pub fn new() -> Self {
42 Self {
43 inner: Arc::new(DashMap::new()),
44 }
45 }
46
47 pub fn insert<K, V>(&self, key: K, value: V)
60 where
61 K: Hash + Eq + Send + Sync + 'static,
62 V: Send + Sync + 'static,
63 {
64 let type_id = TypeId::of::<(K, V)>();
65
66 if !self.inner.contains_key(&type_id) {
67 self.inner.insert(
68 type_id,
69 Box::new(DashMap::<K, V>::new()) as Box<dyn std::any::Any + Send + Sync>,
70 );
71 }
72
73 let storage = self.inner.get(&type_id).expect("just inserted");
74 let map = storage
75 .downcast_ref::<DashMap<K, V>>()
76 .expect("type mismatch in keyed storage");
77 map.insert(key, value);
78 }
79
80 pub fn with<K, V, F, R>(&self, key: &K, f: F) -> Option<R>
84 where
85 K: Hash + Eq + Send + Sync + 'static,
86 V: Send + Sync + 'static,
87 F: FnOnce(&V) -> R,
88 {
89 let type_id = TypeId::of::<(K, V)>();
90 let storage = self.inner.get(&type_id)?;
91 let map = storage.downcast_ref::<DashMap<K, V>>()?;
92 let value_ref = map.get(key)?;
93 Some(f(value_ref.value()))
94 }
95
96 pub fn with_mut<K, V, F, R>(&self, key: &K, f: F) -> Option<R>
100 where
101 K: Hash + Eq + Send + Sync + 'static,
102 V: Send + Sync + 'static,
103 F: FnOnce(&mut V) -> R,
104 {
105 let type_id = TypeId::of::<(K, V)>();
106 let storage = self.inner.get(&type_id)?;
107 let map = storage.downcast_ref::<DashMap<K, V>>()?;
108 let mut value_ref = map.get_mut(key)?;
109 Some(f(value_ref.value_mut()))
110 }
111
112 pub fn get_cloned<K, V>(&self, key: &K) -> Option<V>
114 where
115 K: Hash + Eq + Send + Sync + 'static,
116 V: Clone + Send + Sync + 'static,
117 {
118 self.with::<K, V, _, _>(key, |v: &V| v.clone())
119 }
120
121 pub fn remove<K, V>(&self, key: &K) -> Option<(K, V)>
123 where
124 K: Hash + Eq + Send + Sync + 'static,
125 V: Send + Sync + 'static,
126 {
127 let type_id = TypeId::of::<(K, V)>();
128 let storage = self.inner.get(&type_id)?;
129 let map = storage.downcast_ref::<DashMap<K, V>>()?;
130 map.remove(key)
131 }
132
133 pub fn contains<K, V>(&self, key: &K) -> bool
135 where
136 K: Hash + Eq + Send + Sync + 'static,
137 V: Send + Sync + 'static,
138 {
139 let type_id = TypeId::of::<(K, V)>();
140 if let Some(storage) = self.inner.get(&type_id)
141 && let Some(map) = storage.downcast_ref::<DashMap<K, V>>()
142 {
143 return map.contains_key(key);
144 }
145 false
146 }
147
148 pub fn for_each<K, V, F>(&self, mut f: F)
150 where
151 K: Hash + Eq + Send + Sync + 'static,
152 V: Send + Sync + 'static,
153 F: FnMut(&K, &V),
154 {
155 let type_id = TypeId::of::<(K, V)>();
156 if let Some(storage) = self.inner.get(&type_id)
157 && let Some(map) = storage.downcast_ref::<DashMap<K, V>>()
158 {
159 for entry in map.iter() {
160 let (key, value) = entry.pair();
161 f(key, value);
162 }
163 }
164 }
165
166 pub fn len<K, V>(&self) -> usize
168 where
169 K: Hash + Eq + Send + Sync + 'static,
170 V: Send + Sync + 'static,
171 {
172 let type_id = TypeId::of::<(K, V)>();
173 if let Some(storage) = self.inner.get(&type_id)
174 && let Some(map) = storage.downcast_ref::<DashMap<K, V>>()
175 {
176 return map.len();
177 }
178 0
179 }
180
181 pub fn is_empty<K, V>(&self) -> bool
183 where
184 K: Hash + Eq + Send + Sync + 'static,
185 V: Send + Sync + 'static,
186 {
187 self.len::<K, V>() == 0
188 }
189
190 pub fn contains2<K, V1, V2>(&self, key: &K) -> bool
196 where
197 K: Hash + Eq + Send + Sync + 'static,
198 V1: Send + Sync + 'static,
199 V2: Send + Sync + 'static,
200 {
201 self.contains::<K, V1>(key) && self.contains::<K, V2>(key)
202 }
203
204 pub fn contains3<K, V1, V2, V3>(&self, key: &K) -> bool
206 where
207 K: Hash + Eq + Send + Sync + 'static,
208 V1: Send + Sync + 'static,
209 V2: Send + Sync + 'static,
210 V3: Send + Sync + 'static,
211 {
212 self.contains::<K, V1>(key) && self.contains::<K, V2>(key) && self.contains::<K, V3>(key)
213 }
214
215 pub fn contains4<K, V1, V2, V3, V4>(&self, key: &K) -> bool
217 where
218 K: Hash + Eq + Send + Sync + 'static,
219 V1: Send + Sync + 'static,
220 V2: Send + Sync + 'static,
221 V3: Send + Sync + 'static,
222 V4: Send + Sync + 'static,
223 {
224 self.contains::<K, V1>(key)
225 && self.contains::<K, V2>(key)
226 && self.contains::<K, V3>(key)
227 && self.contains::<K, V4>(key)
228 }
229
230 pub fn with2<K, V1, V2, F, R>(&self, key: &K, f: F) -> Option<R>
232 where
233 K: Hash + Eq + Send + Sync + 'static,
234 V1: Send + Sync + 'static,
235 V2: Send + Sync + 'static,
236 F: FnOnce(&V1, &V2) -> R,
237 {
238 let type_id1 = TypeId::of::<(K, V1)>();
239 let type_id2 = TypeId::of::<(K, V2)>();
240
241 let storage1 = self.inner.get(&type_id1)?;
242 let storage2 = self.inner.get(&type_id2)?;
243
244 let map1 = storage1.downcast_ref::<DashMap<K, V1>>()?;
245 let map2 = storage2.downcast_ref::<DashMap<K, V2>>()?;
246
247 let value1 = map1.get(key)?;
248 let value2 = map2.get(key)?;
249
250 Some(f(value1.value(), value2.value()))
251 }
252
253 pub fn with3<K, V1, V2, V3, F, R>(&self, key: &K, f: F) -> Option<R>
255 where
256 K: Hash + Eq + Send + Sync + 'static,
257 V1: Send + Sync + 'static,
258 V2: Send + Sync + 'static,
259 V3: Send + Sync + 'static,
260 F: FnOnce(&V1, &V2, &V3) -> R,
261 {
262 let type_id1 = TypeId::of::<(K, V1)>();
263 let type_id2 = TypeId::of::<(K, V2)>();
264 let type_id3 = TypeId::of::<(K, V3)>();
265
266 let storage1 = self.inner.get(&type_id1)?;
267 let storage2 = self.inner.get(&type_id2)?;
268 let storage3 = self.inner.get(&type_id3)?;
269
270 let map1 = storage1.downcast_ref::<DashMap<K, V1>>()?;
271 let map2 = storage2.downcast_ref::<DashMap<K, V2>>()?;
272 let map3 = storage3.downcast_ref::<DashMap<K, V3>>()?;
273
274 let value1 = map1.get(key)?;
275 let value2 = map2.get(key)?;
276 let value3 = map3.get(key)?;
277
278 Some(f(value1.value(), value2.value(), value3.value()))
279 }
280
281 pub fn with4<K, V1, V2, V3, V4, F, R>(&self, key: &K, f: F) -> Option<R>
283 where
284 K: Hash + Eq + Send + Sync + 'static,
285 V1: Send + Sync + 'static,
286 V2: Send + Sync + 'static,
287 V3: Send + Sync + 'static,
288 V4: Send + Sync + 'static,
289 F: FnOnce(&V1, &V2, &V3, &V4) -> R,
290 {
291 let type_id1 = TypeId::of::<(K, V1)>();
292 let type_id2 = TypeId::of::<(K, V2)>();
293 let type_id3 = TypeId::of::<(K, V3)>();
294 let type_id4 = TypeId::of::<(K, V4)>();
295
296 let storage1 = self.inner.get(&type_id1)?;
297 let storage2 = self.inner.get(&type_id2)?;
298 let storage3 = self.inner.get(&type_id3)?;
299 let storage4 = self.inner.get(&type_id4)?;
300
301 let map1 = storage1.downcast_ref::<DashMap<K, V1>>()?;
302 let map2 = storage2.downcast_ref::<DashMap<K, V2>>()?;
303 let map3 = storage3.downcast_ref::<DashMap<K, V3>>()?;
304 let map4 = storage4.downcast_ref::<DashMap<K, V4>>()?;
305
306 let value1 = map1.get(key)?;
307 let value2 = map2.get(key)?;
308 let value3 = map3.get(key)?;
309 let value4 = map4.get(key)?;
310
311 Some(f(
312 value1.value(),
313 value2.value(),
314 value3.value(),
315 value4.value(),
316 ))
317 }
318
319 pub fn with2_mut<K, V1, V2, F, R>(&self, key: &K, f: F) -> Option<R>
321 where
322 K: Hash + Eq + Send + Sync + 'static,
323 V1: Send + Sync + 'static,
324 V2: Send + Sync + 'static,
325 F: FnOnce(&mut V1, &mut V2) -> R,
326 {
327 let type_id1 = TypeId::of::<(K, V1)>();
328 let type_id2 = TypeId::of::<(K, V2)>();
329
330 let storage1 = self.inner.get(&type_id1)?;
331 let storage2 = self.inner.get(&type_id2)?;
332
333 let map1 = storage1.downcast_ref::<DashMap<K, V1>>()?;
334 let map2 = storage2.downcast_ref::<DashMap<K, V2>>()?;
335
336 let mut value1 = map1.get_mut(key)?;
337 let mut value2 = map2.get_mut(key)?;
338
339 Some(f(value1.value_mut(), value2.value_mut()))
340 }
341
342 pub fn with3_mut<K, V1, V2, V3, F, R>(&self, key: &K, f: F) -> Option<R>
344 where
345 K: Hash + Eq + Send + Sync + 'static,
346 V1: Send + Sync + 'static,
347 V2: Send + Sync + 'static,
348 V3: Send + Sync + 'static,
349 F: FnOnce(&mut V1, &mut V2, &mut V3) -> R,
350 {
351 let type_id1 = TypeId::of::<(K, V1)>();
352 let type_id2 = TypeId::of::<(K, V2)>();
353 let type_id3 = TypeId::of::<(K, V3)>();
354
355 let storage1 = self.inner.get(&type_id1)?;
356 let storage2 = self.inner.get(&type_id2)?;
357 let storage3 = self.inner.get(&type_id3)?;
358
359 let map1 = storage1.downcast_ref::<DashMap<K, V1>>()?;
360 let map2 = storage2.downcast_ref::<DashMap<K, V2>>()?;
361 let map3 = storage3.downcast_ref::<DashMap<K, V3>>()?;
362
363 let mut value1 = map1.get_mut(key)?;
364 let mut value2 = map2.get_mut(key)?;
365 let mut value3 = map3.get_mut(key)?;
366
367 Some(f(
368 value1.value_mut(),
369 value2.value_mut(),
370 value3.value_mut(),
371 ))
372 }
373
374 pub fn with4_mut<K, V1, V2, V3, V4, F, R>(&self, key: &K, f: F) -> Option<R>
376 where
377 K: Hash + Eq + Send + Sync + 'static,
378 V1: Send + Sync + 'static,
379 V2: Send + Sync + 'static,
380 V3: Send + Sync + 'static,
381 V4: Send + Sync + 'static,
382 F: FnOnce(&mut V1, &mut V2, &mut V3, &mut V4) -> R,
383 {
384 let type_id1 = TypeId::of::<(K, V1)>();
385 let type_id2 = TypeId::of::<(K, V2)>();
386 let type_id3 = TypeId::of::<(K, V3)>();
387 let type_id4 = TypeId::of::<(K, V4)>();
388
389 let storage1 = self.inner.get(&type_id1)?;
390 let storage2 = self.inner.get(&type_id2)?;
391 let storage3 = self.inner.get(&type_id3)?;
392 let storage4 = self.inner.get(&type_id4)?;
393
394 let map1 = storage1.downcast_ref::<DashMap<K, V1>>()?;
395 let map2 = storage2.downcast_ref::<DashMap<K, V2>>()?;
396 let map3 = storage3.downcast_ref::<DashMap<K, V3>>()?;
397 let map4 = storage4.downcast_ref::<DashMap<K, V4>>()?;
398
399 let mut value1 = map1.get_mut(key)?;
400 let mut value2 = map2.get_mut(key)?;
401 let mut value3 = map3.get_mut(key)?;
402 let mut value4 = map4.get_mut(key)?;
403
404 Some(f(
405 value1.value_mut(),
406 value2.value_mut(),
407 value3.value_mut(),
408 value4.value_mut(),
409 ))
410 }
411
412 pub fn get2_cloned<K, V1, V2>(&self, key: &K) -> Option<(V1, V2)>
418 where
419 K: Hash + Eq + Send + Sync + 'static,
420 V1: Clone + Send + Sync + 'static,
421 V2: Clone + Send + Sync + 'static,
422 {
423 self.with2::<K, V1, V2, _, _>(key, |v1, v2| (v1.clone(), v2.clone()))
424 }
425
426 pub fn get3_cloned<K, V1, V2, V3>(&self, key: &K) -> Option<(V1, V2, V3)>
428 where
429 K: Hash + Eq + Send + Sync + 'static,
430 V1: Clone + Send + Sync + 'static,
431 V2: Clone + Send + Sync + 'static,
432 V3: Clone + Send + Sync + 'static,
433 {
434 self.with3::<K, V1, V2, V3, _, _>(key, |v1, v2, v3| (v1.clone(), v2.clone(), v3.clone()))
435 }
436
437 pub fn get4_cloned<K, V1, V2, V3, V4>(&self, key: &K) -> Option<(V1, V2, V3, V4)>
439 where
440 K: Hash + Eq + Send + Sync + 'static,
441 V1: Clone + Send + Sync + 'static,
442 V2: Clone + Send + Sync + 'static,
443 V3: Clone + Send + Sync + 'static,
444 V4: Clone + Send + Sync + 'static,
445 {
446 self.with4::<K, V1, V2, V3, V4, _, _>(key, |v1, v2, v3, v4| {
447 (v1.clone(), v2.clone(), v3.clone(), v4.clone())
448 })
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 #[derive(Clone, Debug, PartialEq)]
457 struct Session {
458 user_id: String,
459 }
460
461 #[test]
462 fn basic_operations() {
463 let map = CodexMap::new();
464
465 map.insert(
466 "user_1",
467 Session {
468 user_id: "1".into(),
469 },
470 );
471 map.insert(
472 "user_2",
473 Session {
474 user_id: "2".into(),
475 },
476 );
477
478 assert!(map.contains::<&str, Session>(&"user_1"));
479 assert_eq!(map.len::<&str, Session>(), 2);
480
481 let user_id = map
482 .with::<&str, Session, _, _>(&"user_1", |session| session.user_id.clone())
483 .unwrap();
484 assert_eq!(user_id, "1");
485 }
486
487 #[test]
488 fn with_mut() {
489 let map = CodexMap::new();
490 map.insert(
491 "user_1",
492 Session {
493 user_id: "1".into(),
494 },
495 );
496
497 map.with_mut::<&str, Session, _, _>(&"user_1", |session| {
498 session.user_id = "updated".into();
499 });
500
501 let user_id = map
502 .with::<&str, Session, _, _>(&"user_1", |session| session.user_id.clone())
503 .unwrap();
504 assert_eq!(user_id, "updated");
505 }
506
507 #[test]
508 fn remove() {
509 let map = CodexMap::new();
510 map.insert(
511 "user_1",
512 Session {
513 user_id: "1".into(),
514 },
515 );
516
517 let (key, session) = map.remove::<&str, Session>(&"user_1").unwrap();
518 assert_eq!(key, "user_1");
519 assert_eq!(session.user_id, "1");
520 assert!(!map.contains::<&str, Session>(&"user_1"));
521 }
522
523 #[test]
524 fn for_each() {
525 let map = CodexMap::new();
526 map.insert(
527 "user_1",
528 Session {
529 user_id: "1".into(),
530 },
531 );
532 map.insert(
533 "user_2",
534 Session {
535 user_id: "2".into(),
536 },
537 );
538
539 let mut count = 0;
540 map.for_each::<&str, Session, _>(|_key, session| {
541 assert!(session.user_id == "1" || session.user_id == "2");
542 count += 1;
543 });
544 assert_eq!(count, 2);
545 }
546
547 #[test]
548 fn type_isolation() {
549 let map = CodexMap::new();
550
551 #[derive(Clone)]
552 struct Config {
553 debug: bool,
554 }
555
556 map.insert(
557 "key1",
558 Session {
559 user_id: "1".into(),
560 },
561 );
562 map.insert("key1", Config { debug: true });
563
564 assert!(map.contains::<&str, Session>(&"key1"));
565 assert!(map.contains::<&str, Config>(&"key1"));
566 }
567
568 #[test]
569 fn clone_shares_storage() {
570 let map = CodexMap::new();
571 map.insert(
572 "user_1",
573 Session {
574 user_id: "1".into(),
575 },
576 );
577
578 let map2 = map.clone();
579
580 assert!(map2.contains::<&str, Session>(&"user_1"));
581
582 map2.insert(
583 "user_2",
584 Session {
585 user_id: "2".into(),
586 },
587 );
588
589 assert!(map.contains::<&str, Session>(&"user_2"));
590 assert_eq!(map.len::<&str, Session>(), 2);
591 }
592
593 #[tokio::test]
594 async fn clone_across_tasks() {
595 let map = CodexMap::new();
596 map.insert("initial", 42u32);
597
598 let map_clone = map.clone();
599
600 let handle = tokio::spawn(async move {
601 map_clone.insert("from_task", 100u32);
602 map_clone.get_cloned::<&str, u32>(&"initial")
603 });
604
605 let value = handle.await.unwrap();
606 assert_eq!(value, Some(42));
607
608 assert_eq!(map.get_cloned::<&str, u32>(&"from_task"), Some(100));
609 }
610
611 #[derive(Clone, Debug, PartialEq)]
616 struct Position {
617 x: f32,
618 y: f32,
619 }
620
621 #[derive(Clone, Debug, PartialEq)]
622 struct Velocity {
623 dx: f32,
624 dy: f32,
625 }
626
627 #[derive(Clone, Debug, PartialEq)]
628 struct Health {
629 hp: i32,
630 }
631
632 #[derive(Clone, Debug, PartialEq)]
633 struct Name {
634 value: String,
635 }
636
637 #[test]
638 fn composition_contains2() {
639 let map = CodexMap::new();
640
641 map.insert("entity_1", Position { x: 0.0, y: 0.0 });
642 map.insert("entity_1", Velocity { dx: 1.0, dy: 0.5 });
643 map.insert("entity_2", Position { x: 5.0, y: 3.0 });
644
645 assert!(map.contains2::<&str, Position, Velocity>(&"entity_1"));
647
648 assert!(!map.contains2::<&str, Position, Velocity>(&"entity_2"));
650
651 assert!(!map.contains2::<&str, Position, Velocity>(&"entity_3"));
653 }
654
655 #[test]
656 fn composition_contains3() {
657 let map = CodexMap::new();
658
659 map.insert("entity_1", Position { x: 0.0, y: 0.0 });
660 map.insert("entity_1", Velocity { dx: 1.0, dy: 0.5 });
661 map.insert("entity_1", Health { hp: 100 });
662
663 assert!(map.contains3::<&str, Position, Velocity, Health>(&"entity_1"));
664
665 map.insert("entity_2", Position { x: 5.0, y: 3.0 });
666 map.insert("entity_2", Velocity { dx: -1.0, dy: 2.0 });
667
668 assert!(!map.contains3::<&str, Position, Velocity, Health>(&"entity_2"));
670 }
671
672 #[test]
673 fn composition_with2() {
674 let map = CodexMap::new();
675
676 map.insert("player", Position { x: 0.0, y: 0.0 });
677 map.insert("player", Velocity { dx: 1.0, dy: 0.5 });
678
679 let result = map
681 .with2::<&str, Position, Velocity, _, _>(&"player", |pos, vel| {
682 format!("at ({}, {}) moving ({}, {})", pos.x, pos.y, vel.dx, vel.dy)
683 })
684 .unwrap();
685
686 assert_eq!(result, "at (0, 0) moving (1, 0.5)");
687 }
688
689 #[test]
690 fn composition_with2_mut() {
691 let map = CodexMap::new();
692
693 map.insert("player", Position { x: 0.0, y: 0.0 });
694 map.insert("player", Velocity { dx: 1.0, dy: 0.5 });
695
696 map.with2_mut::<&str, Position, Velocity, _, _>(&"player", |pos, vel| {
698 pos.x += vel.dx;
699 pos.y += vel.dy;
700 });
701
702 let pos = map.get_cloned::<&str, Position>(&"player").unwrap();
703 assert_eq!(pos, Position { x: 1.0, y: 0.5 });
704 }
705
706 #[test]
707 fn composition_with3() {
708 let map = CodexMap::new();
709
710 map.insert("player", Position { x: 10.0, y: 20.0 });
711 map.insert("player", Velocity { dx: 1.0, dy: 2.0 });
712 map.insert("player", Health { hp: 100 });
713
714 let result = map
715 .with3::<&str, Position, Velocity, Health, _, _>(&"player", |pos, vel, health| {
716 format!(
717 "Player at ({}, {}) moving ({}, {}) with {} HP",
718 pos.x, pos.y, vel.dx, vel.dy, health.hp
719 )
720 })
721 .unwrap();
722
723 assert_eq!(result, "Player at (10, 20) moving (1, 2) with 100 HP");
724 }
725
726 #[test]
727 fn composition_with3_mut() {
728 let map = CodexMap::new();
729
730 map.insert("player", Position { x: 0.0, y: 0.0 });
731 map.insert("player", Velocity { dx: 2.0, dy: 3.0 });
732 map.insert("player", Health { hp: 100 });
733
734 map.with3_mut::<&str, Position, Velocity, Health, _, _>(&"player", |pos, vel, health| {
736 pos.x += vel.dx;
737 pos.y += vel.dy;
738 vel.dx *= 0.9; vel.dy *= 0.9;
740 health.hp -= 1; });
742
743 let pos = map.get_cloned::<&str, Position>(&"player").unwrap();
744 let vel = map.get_cloned::<&str, Velocity>(&"player").unwrap();
745 let health = map.get_cloned::<&str, Health>(&"player").unwrap();
746
747 assert_eq!(pos, Position { x: 2.0, y: 3.0 });
748 assert!((vel.dx - 1.8).abs() < 0.001);
749 assert!((vel.dy - 2.7).abs() < 0.001);
750 assert_eq!(health.hp, 99);
751 }
752
753 #[test]
754 fn composition_with4() {
755 let map = CodexMap::new();
756
757 map.insert(
758 "player",
759 Name {
760 value: "Hero".to_string(),
761 },
762 );
763 map.insert("player", Position { x: 10.0, y: 20.0 });
764 map.insert("player", Velocity { dx: 1.0, dy: 2.0 });
765 map.insert("player", Health { hp: 100 });
766
767 let result = map
768 .with4::<&str, Name, Position, Velocity, Health, _, _>(
769 &"player",
770 |name, pos, vel, health| {
771 format!(
772 "{} at ({}, {}) moving ({}, {}) with {} HP",
773 name.value, pos.x, pos.y, vel.dx, vel.dy, health.hp
774 )
775 },
776 )
777 .unwrap();
778
779 assert_eq!(result, "Hero at (10, 20) moving (1, 2) with 100 HP");
780 }
781
782 #[test]
783 fn composition_get2_cloned() {
784 let map = CodexMap::new();
785
786 map.insert("player", Position { x: 5.0, y: 10.0 });
787 map.insert("player", Velocity { dx: 2.0, dy: 3.0 });
788
789 let (pos, vel) = map
791 .get2_cloned::<&str, Position, Velocity>(&"player")
792 .unwrap();
793
794 assert_eq!(pos, Position { x: 5.0, y: 10.0 });
795 assert_eq!(vel, Velocity { dx: 2.0, dy: 3.0 });
796
797 assert!(map.contains::<&str, Position>(&"player"));
799 assert!(map.contains::<&str, Velocity>(&"player"));
800 }
801
802 #[test]
803 fn composition_get3_cloned() {
804 let map = CodexMap::new();
805
806 map.insert("player", Position { x: 5.0, y: 10.0 });
807 map.insert("player", Velocity { dx: 2.0, dy: 3.0 });
808 map.insert("player", Health { hp: 100 });
809
810 let (pos, vel, health) = map
811 .get3_cloned::<&str, Position, Velocity, Health>(&"player")
812 .unwrap();
813
814 assert_eq!(pos, Position { x: 5.0, y: 10.0 });
815 assert_eq!(vel, Velocity { dx: 2.0, dy: 3.0 });
816 assert_eq!(health, Health { hp: 100 });
817 }
818
819 #[test]
820 fn composition_get4_cloned() {
821 let map = CodexMap::new();
822
823 map.insert(
824 "player",
825 Name {
826 value: "Hero".to_string(),
827 },
828 );
829 map.insert("player", Position { x: 5.0, y: 10.0 });
830 map.insert("player", Velocity { dx: 2.0, dy: 3.0 });
831 map.insert("player", Health { hp: 100 });
832
833 let (name, pos, vel, health) = map
834 .get4_cloned::<&str, Name, Position, Velocity, Health>(&"player")
835 .unwrap();
836
837 assert_eq!(
838 name,
839 Name {
840 value: "Hero".to_string()
841 }
842 );
843 assert_eq!(pos, Position { x: 5.0, y: 10.0 });
844 assert_eq!(vel, Velocity { dx: 2.0, dy: 3.0 });
845 assert_eq!(health, Health { hp: 100 });
846 }
847
848 #[test]
849 fn composition_get_cloned_can_be_moved() {
850 let map = CodexMap::new();
851
852 map.insert("player", Position { x: 5.0, y: 10.0 });
853 map.insert("player", Velocity { dx: 2.0, dy: 3.0 });
854
855 let (pos, vel) = map
857 .get2_cloned::<&str, Position, Velocity>(&"player")
858 .unwrap();
859
860 fn process_components(p: Position, v: Velocity) -> f32 {
862 p.x + v.dx
863 }
864
865 let result = process_components(pos, vel);
866 assert_eq!(result, 7.0);
867 }
868
869 #[test]
870 fn composition_missing_component_returns_none() {
871 let map = CodexMap::new();
872
873 map.insert("entity", Position { x: 0.0, y: 0.0 });
874 let result = map.with2::<&str, Position, Velocity, _, _>(&"entity", |_, _| ());
878 assert!(result.is_none());
879
880 let result = map.get2_cloned::<&str, Position, Velocity>(&"entity");
882 assert!(result.is_none());
883 }
884
885 #[test]
886 fn composition_multiple_entities() {
887 let map = CodexMap::new();
888
889 map.insert("entity_1", Position { x: 0.0, y: 0.0 });
891 map.insert("entity_1", Velocity { dx: 1.0, dy: 0.5 });
892
893 map.insert("entity_2", Position { x: 10.0, y: 20.0 });
895 map.insert("entity_2", Velocity { dx: -1.0, dy: -2.0 });
896
897 map.insert("entity_3", Position { x: 5.0, y: 5.0 });
899
900 for entity_id in &["entity_1", "entity_2", "entity_3"] {
902 map.with2_mut::<&str, Position, Velocity, _, _>(entity_id, |pos, vel| {
903 pos.x += vel.dx;
904 pos.y += vel.dy;
905 });
906 }
907
908 let pos1 = map.get_cloned::<&str, Position>(&"entity_1").unwrap();
910 assert_eq!(pos1, Position { x: 1.0, y: 0.5 });
911
912 let pos2 = map.get_cloned::<&str, Position>(&"entity_2").unwrap();
913 assert_eq!(pos2, Position { x: 9.0, y: 18.0 });
914
915 let pos3 = map.get_cloned::<&str, Position>(&"entity_3").unwrap();
917 assert_eq!(pos3, Position { x: 5.0, y: 5.0 });
918 }
919
920 #[test]
921 fn composition_ecs_physics_simulation() {
922 let map = CodexMap::new();
923
924 let entities = vec!["ball", "player", "enemy"];
926
927 map.insert("ball", Position { x: 0.0, y: 10.0 });
929 map.insert("ball", Velocity { dx: 5.0, dy: -2.0 });
930
931 map.insert("player", Position { x: 0.0, y: 0.0 });
932 map.insert("player", Velocity { dx: 1.0, dy: 0.0 });
933 map.insert("player", Health { hp: 100 });
934
935 map.insert("enemy", Position { x: 20.0, y: 5.0 });
936 map.insert("enemy", Velocity { dx: -0.5, dy: 0.0 });
937 map.insert("enemy", Health { hp: 50 });
938
939 for entity in &entities {
941 map.with2_mut::<&str, Position, Velocity, _, _>(entity, |pos, vel| {
943 pos.x += vel.dx;
944 pos.y += vel.dy;
945 });
946 }
947
948 let ball_pos = map.get_cloned::<&str, Position>(&"ball").unwrap();
950 assert_eq!(ball_pos, Position { x: 5.0, y: 8.0 });
951
952 let player_pos = map.get_cloned::<&str, Position>(&"player").unwrap();
953 assert_eq!(player_pos, Position { x: 1.0, y: 0.0 });
954
955 let enemy_pos = map.get_cloned::<&str, Position>(&"enemy").unwrap();
956 assert_eq!(enemy_pos, Position { x: 19.5, y: 5.0 });
957 }
958}