concinnity_core/ecs/
resource.rs1use alloc::boxed::Box;
11use alloc::collections::BTreeMap;
12use core::any::{Any, TypeId};
13
14#[derive(Default)]
15pub struct Resources {
17 map: BTreeMap<TypeId, Box<dyn Any + Send>>,
18}
19
20impl Resources {
21 pub fn new() -> Resources {
23 Resources::default()
24 }
25
26 pub fn insert<T: Any + Send>(&mut self, value: T) -> Option<T> {
30 if let Some(slot) = self.map.get_mut(&TypeId::of::<T>()) {
31 let existing = (slot.as_mut() as &mut dyn Any)
32 .downcast_mut::<T>()
33 .expect("resource slot type matches its TypeId key");
34 return Some(core::mem::replace(existing, value));
35 }
36 self.map.insert(TypeId::of::<T>(), Box::new(value));
37 None
38 }
39
40 pub fn get<T: Any>(&self) -> Option<&T> {
42 self.map
43 .get(&TypeId::of::<T>())
44 .and_then(|boxed| (boxed.as_ref() as &dyn Any).downcast_ref::<T>())
45 }
46
47 pub fn get_mut<T: Any>(&mut self) -> Option<&mut T> {
49 self.map
50 .get_mut(&TypeId::of::<T>())
51 .and_then(|boxed| (boxed.as_mut() as &mut dyn Any).downcast_mut::<T>())
52 }
53
54 pub fn remove<T: Any>(&mut self) -> Option<T> {
56 self.map.remove(&TypeId::of::<T>()).and_then(downcast::<T>)
57 }
58
59 pub fn take<T: Any + Send + Default>(&mut self) -> Option<T> {
63 self.get_mut::<T>().map(core::mem::take)
64 }
65
66 pub fn contains<T: Any>(&self) -> bool {
68 self.map.contains_key(&TypeId::of::<T>())
69 }
70}
71
72fn downcast<T: Any>(boxed: Box<dyn Any + Send>) -> Option<T> {
73 (boxed as Box<dyn Any>).downcast::<T>().ok().map(|v| *v)
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[derive(Debug, PartialEq, Default)]
81 struct FrameTime(f32);
82
83 #[test]
84 fn insert_get_and_remove_by_type() {
85 let mut resources = Resources::new();
86 assert!(!resources.contains::<FrameTime>());
87 assert_eq!(resources.insert(FrameTime(0.016)), None);
88 assert!(resources.contains::<FrameTime>());
89 assert_eq!(resources.get::<FrameTime>(), Some(&FrameTime(0.016)));
90 assert_eq!(resources.remove::<FrameTime>(), Some(FrameTime(0.016)));
91 assert!(!resources.contains::<FrameTime>());
92 }
93
94 #[test]
95 fn insert_returns_previous_value() {
96 let mut resources = Resources::new();
97 resources.insert(FrameTime(1.0));
98 assert_eq!(resources.insert(FrameTime(2.0)), Some(FrameTime(1.0)));
99 }
100
101 #[test]
102 fn get_mut_edits_in_place() {
103 let mut resources = Resources::new();
104 resources.insert(FrameTime(1.0));
105 resources.get_mut::<FrameTime>().unwrap().0 = 5.0;
106 assert_eq!(resources.get::<FrameTime>(), Some(&FrameTime(5.0)));
107 }
108
109 #[test]
110 fn insert_replaces_in_place_without_reboxing() {
111 let mut resources = Resources::new();
112 resources.insert(FrameTime(1.0));
113 let before = resources.get::<FrameTime>().unwrap() as *const FrameTime;
114 assert_eq!(resources.insert(FrameTime(2.0)), Some(FrameTime(1.0)));
115 let after = resources.get::<FrameTime>().unwrap() as *const FrameTime;
116 assert_eq!(before, after, "republish must reuse the existing box");
117 }
118
119 #[test]
120 fn take_leaves_a_default_parked_in_the_slot() {
121 let mut resources = Resources::new();
122 assert_eq!(resources.take::<FrameTime>(), None);
123 resources.insert(FrameTime(3.0));
124 let before = resources.get::<FrameTime>().unwrap() as *const FrameTime;
125 assert_eq!(resources.take::<FrameTime>(), Some(FrameTime(3.0)));
126 let after = resources.get::<FrameTime>().unwrap() as *const FrameTime;
127 assert_eq!(before, after, "take must leave the box parked");
128 assert_eq!(resources.get::<FrameTime>(), Some(&FrameTime(0.0)));
129 }
130
131 #[test]
132 fn distinct_types_are_independent() {
133 let mut resources = Resources::new();
134 resources.insert(FrameTime(1.0));
135 resources.insert(7u32);
136 assert_eq!(resources.get::<FrameTime>(), Some(&FrameTime(1.0)));
137 assert_eq!(resources.get::<u32>(), Some(&7));
138 }
139}