Skip to main content

concinnity_core/ecs/
resource.rs

1// Type-keyed singleton store: each type has at most one instance, fetched by
2// type. The home for engine-wide singletons (frame input, the render backend,
3// the profiler) that would otherwise be faked as one-element collections.
4//
5// Values are required to be `Send` so the world that owns the store can move
6// to a simulation thread. Thread-affine state (a GPU backend) may still be
7// stored behind a `Send` handle, but its owner must keep it on the thread its
8// invariants require.
9
10use alloc::boxed::Box;
11use alloc::collections::BTreeMap;
12use core::any::{Any, TypeId};
13
14#[derive(Default)]
15/// Type-keyed singleton storage: one value per resource type.
16pub struct Resources {
17    map: BTreeMap<TypeId, Box<dyn Any + Send>>,
18}
19
20impl Resources {
21    /// An empty store.
22    pub fn new() -> Resources {
23        Resources::default()
24    }
25
26    /// Insert a resource, returning the previous instance of the same type if
27    /// one was present. Replaces in place when the type is already present, so
28    /// a per-frame republish reuses the existing allocation.
29    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    /// Borrow the resource of type `T`, if one is present.
41    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    /// Mutably borrow the resource of type `T`, if one is present.
48    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    /// Remove and return the resource of type `T`, if one is present.
55    pub fn remove<T: Any>(&mut self) -> Option<T> {
56        self.map.remove(&TypeId::of::<T>()).and_then(downcast::<T>)
57    }
58
59    /// Take the resource value, leaving `T::default()` parked in its slot so a
60    /// later `insert` republish reuses the allocation. `None` when the type was
61    /// never inserted; a per-frame take/put cycle never re-boxes.
62    pub fn take<T: Any + Send + Default>(&mut self) -> Option<T> {
63        self.get_mut::<T>().map(core::mem::take)
64    }
65
66    /// Whether a resource of type `T` is present.
67    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}