Skip to main content

astrelis/
resource.rs

1//! Resource management for the engine.
2//!
3//! Resources are shared data that can be accessed by plugins and game systems.
4//! They provide a type-safe way to store and retrieve arbitrary data.
5
6use std::any::{Any, TypeId};
7use std::collections::HashMap;
8
9/// Marker trait for types that can be stored as engine resources.
10///
11/// Resources must be `Send + Sync` to allow safe access across threads.
12pub trait Resource: Send + Sync + 'static {}
13
14// Blanket implementation for all eligible types
15impl<T: Send + Sync + 'static> Resource for T {}
16
17/// Type-erased resource storage.
18struct ResourceEntry {
19    data: Box<dyn Any + Send + Sync>,
20    type_name: &'static str,
21}
22
23/// Container for engine resources.
24///
25/// Resources are stored by type and can be accessed through typed getters.
26/// Each resource type can only have one instance.
27///
28/// # Example
29///
30/// ```
31/// use astrelis::resource::Resources;
32///
33/// struct GameConfig {
34///     title: String,
35///     max_fps: u32,
36/// }
37///
38/// let mut resources = Resources::new();
39/// resources.insert(GameConfig {
40///     title: "My Game".to_string(),
41///     max_fps: 60,
42/// });
43///
44/// let config = resources.get::<GameConfig>().unwrap();
45/// assert_eq!(config.title, "My Game");
46/// ```
47#[derive(Default)]
48pub struct Resources {
49    storage: HashMap<TypeId, ResourceEntry>,
50}
51
52impl Resources {
53    /// Create a new empty resource container.
54    pub fn new() -> Self {
55        Self {
56            storage: HashMap::new(),
57        }
58    }
59
60    /// Insert a resource, replacing any existing resource of the same type.
61    ///
62    /// Returns the previous resource if one existed.
63    pub fn insert<R: Resource>(&mut self, resource: R) -> Option<R> {
64        let type_id = TypeId::of::<R>();
65        let type_name = std::any::type_name::<R>();
66
67        let entry = ResourceEntry {
68            data: Box::new(resource),
69            type_name,
70        };
71
72        self.storage
73            .insert(type_id, entry)
74            .and_then(|old| old.data.downcast::<R>().ok().map(|b| *b))
75    }
76
77    /// Get a reference to a resource.
78    pub fn get<R: Resource>(&self) -> Option<&R> {
79        let type_id = TypeId::of::<R>();
80        self.storage
81            .get(&type_id)
82            .and_then(|entry| entry.data.downcast_ref())
83    }
84
85    /// Get a mutable reference to a resource.
86    pub fn get_mut<R: Resource>(&mut self) -> Option<&mut R> {
87        let type_id = TypeId::of::<R>();
88        self.storage
89            .get_mut(&type_id)
90            .and_then(|entry| entry.data.downcast_mut())
91    }
92
93    /// Remove a resource and return it.
94    pub fn remove<R: Resource>(&mut self) -> Option<R> {
95        let type_id = TypeId::of::<R>();
96        self.storage
97            .remove(&type_id)
98            .and_then(|entry| entry.data.downcast::<R>().ok().map(|b| *b))
99    }
100
101    /// Check if a resource exists.
102    pub fn contains<R: Resource>(&self) -> bool {
103        let type_id = TypeId::of::<R>();
104        self.storage.contains_key(&type_id)
105    }
106
107    /// Get the number of resources.
108    pub fn len(&self) -> usize {
109        self.storage.len()
110    }
111
112    /// Check if there are no resources.
113    pub fn is_empty(&self) -> bool {
114        self.storage.is_empty()
115    }
116
117    /// Clear all resources.
118    pub fn clear(&mut self) {
119        self.storage.clear();
120    }
121
122    /// Get or insert a resource with a default value.
123    pub fn get_or_insert_with<R: Resource>(&mut self, f: impl FnOnce() -> R) -> &mut R {
124        let type_id = TypeId::of::<R>();
125
126        if !self.storage.contains_key(&type_id) {
127            self.insert(f());
128        }
129
130        self.get_mut::<R>().unwrap()
131    }
132
133    /// Get or insert a resource with its default value.
134    pub fn get_or_default<R: Resource + Default>(&mut self) -> &mut R {
135        self.get_or_insert_with(R::default)
136    }
137
138    /// List all resource type names (for debugging).
139    pub fn type_names(&self) -> impl Iterator<Item = &'static str> + '_ {
140        self.storage.values().map(|entry| entry.type_name)
141    }
142}
143
144impl std::fmt::Debug for Resources {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        f.debug_struct("Resources")
147            .field("count", &self.storage.len())
148            .field("types", &self.type_names().collect::<Vec<_>>())
149            .finish()
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_insert_and_get() {
159        let mut resources = Resources::new();
160        resources.insert(42i32);
161        resources.insert("hello".to_string());
162
163        assert_eq!(*resources.get::<i32>().unwrap(), 42);
164        assert_eq!(resources.get::<String>().unwrap(), "hello");
165    }
166
167    #[test]
168    fn test_get_mut() {
169        let mut resources = Resources::new();
170        resources.insert(vec![1, 2, 3]);
171
172        resources.get_mut::<Vec<i32>>().unwrap().push(4);
173        assert_eq!(resources.get::<Vec<i32>>().unwrap(), &vec![1, 2, 3, 4]);
174    }
175
176    #[test]
177    fn test_replace() {
178        let mut resources = Resources::new();
179        resources.insert(10i32);
180        let old = resources.insert(20i32);
181
182        assert_eq!(old, Some(10));
183        assert_eq!(*resources.get::<i32>().unwrap(), 20);
184    }
185
186    #[test]
187    fn test_remove() {
188        let mut resources = Resources::new();
189        resources.insert(42i32);
190
191        let removed = resources.remove::<i32>();
192        assert_eq!(removed, Some(42));
193        assert!(resources.get::<i32>().is_none());
194    }
195
196    #[test]
197    fn test_contains() {
198        let mut resources = Resources::new();
199        assert!(!resources.contains::<i32>());
200
201        resources.insert(42i32);
202        assert!(resources.contains::<i32>());
203    }
204
205    #[test]
206    fn test_get_or_default() {
207        let mut resources = Resources::new();
208
209        let val = resources.get_or_default::<Vec<i32>>();
210        val.push(1);
211
212        assert_eq!(resources.get::<Vec<i32>>().unwrap(), &vec![1]);
213    }
214
215    #[test]
216    fn test_get_or_insert_with() {
217        let mut resources = Resources::new();
218        let mut called = false;
219
220        resources.get_or_insert_with(|| {
221            called = true;
222            42i32
223        });
224        assert!(called);
225
226        called = false;
227        resources.get_or_insert_with(|| {
228            called = true;
229            100i32
230        });
231        assert!(!called); // Should not be called again
232        assert_eq!(*resources.get::<i32>().unwrap(), 42);
233    }
234}