Skip to main content

oxihuman_core/
resource_manager.rs

1//! Resource lifecycle management (load/unload/reference counting).
2
3#[allow(dead_code)]
4#[derive(Clone, PartialEq, Debug)]
5pub enum ResourceState {
6    Unloaded,
7    Loading,
8    Loaded,
9    Failed(String),
10}
11
12#[allow(dead_code)]
13pub struct Resource {
14    pub id: u32,
15    pub key: String,
16    pub resource_type: String,
17    pub state: ResourceState,
18    pub data: Option<Vec<u8>>,
19    pub ref_count: u32,
20    pub size_bytes: usize,
21}
22
23#[allow(dead_code)]
24pub struct ResourceManager {
25    pub resources: Vec<Resource>,
26    pub next_id: u32,
27    pub total_loaded_bytes: usize,
28}
29
30#[allow(dead_code)]
31pub fn new_resource_manager() -> ResourceManager {
32    ResourceManager {
33        resources: Vec::new(),
34        next_id: 1,
35        total_loaded_bytes: 0,
36    }
37}
38
39#[allow(dead_code)]
40pub fn register_resource(mgr: &mut ResourceManager, key: &str, rtype: &str) -> u32 {
41    let id = mgr.next_id;
42    mgr.next_id += 1;
43    mgr.resources.push(Resource {
44        id,
45        key: key.to_string(),
46        resource_type: rtype.to_string(),
47        state: ResourceState::Unloaded,
48        data: None,
49        ref_count: 0,
50        size_bytes: 0,
51    });
52    id
53}
54
55#[allow(dead_code)]
56pub fn load_resource(mgr: &mut ResourceManager, id: u32, data: Vec<u8>) {
57    if let Some(r) = mgr.resources.iter_mut().find(|r| r.id == id) {
58        let size = data.len();
59        // subtract old size if already loaded
60        if r.state == ResourceState::Loaded {
61            mgr.total_loaded_bytes = mgr.total_loaded_bytes.saturating_sub(r.size_bytes);
62        }
63        r.size_bytes = size;
64        r.data = Some(data);
65        r.state = ResourceState::Loaded;
66        mgr.total_loaded_bytes += size;
67    }
68}
69
70#[allow(dead_code)]
71pub fn fail_resource(mgr: &mut ResourceManager, id: u32, reason: &str) {
72    if let Some(r) = mgr.resources.iter_mut().find(|r| r.id == id) {
73        if r.state == ResourceState::Loaded {
74            mgr.total_loaded_bytes = mgr.total_loaded_bytes.saturating_sub(r.size_bytes);
75        }
76        r.state = ResourceState::Failed(reason.to_string());
77        r.data = None;
78        r.size_bytes = 0;
79    }
80}
81
82#[allow(dead_code)]
83pub fn unload_resource(mgr: &mut ResourceManager, id: u32) {
84    if let Some(r) = mgr.resources.iter_mut().find(|r| r.id == id) {
85        if r.state == ResourceState::Loaded {
86            mgr.total_loaded_bytes = mgr.total_loaded_bytes.saturating_sub(r.size_bytes);
87        }
88        r.data = None;
89        r.size_bytes = 0;
90        r.state = ResourceState::Unloaded;
91    }
92}
93
94#[allow(dead_code)]
95pub fn get_resource(mgr: &ResourceManager, id: u32) -> Option<&Resource> {
96    mgr.resources.iter().find(|r| r.id == id)
97}
98
99#[allow(dead_code)]
100pub fn get_by_key<'a>(mgr: &'a ResourceManager, key: &str) -> Option<&'a Resource> {
101    mgr.resources.iter().find(|r| r.key == key)
102}
103
104#[allow(dead_code)]
105pub fn retain_resource(mgr: &mut ResourceManager, id: u32) {
106    if let Some(r) = mgr.resources.iter_mut().find(|r| r.id == id) {
107        r.ref_count += 1;
108    }
109}
110
111#[allow(dead_code)]
112pub fn release_resource(mgr: &mut ResourceManager, id: u32) {
113    let should_unload = if let Some(r) = mgr.resources.iter_mut().find(|r| r.id == id) {
114        if r.ref_count > 0 {
115            r.ref_count -= 1;
116        }
117        r.ref_count == 0 && r.state == ResourceState::Loaded
118    } else {
119        false
120    };
121    if should_unload {
122        unload_resource(mgr, id);
123    }
124}
125
126#[allow(dead_code)]
127pub fn loaded_count(mgr: &ResourceManager) -> usize {
128    mgr.resources
129        .iter()
130        .filter(|r| r.state == ResourceState::Loaded)
131        .count()
132}
133
134#[allow(dead_code)]
135pub fn failed_count(mgr: &ResourceManager) -> usize {
136    mgr.resources
137        .iter()
138        .filter(|r| matches!(r.state, ResourceState::Failed(_)))
139        .count()
140}
141
142#[allow(dead_code)]
143pub fn total_memory(mgr: &ResourceManager) -> usize {
144    mgr.total_loaded_bytes
145}
146
147#[allow(dead_code)]
148pub fn garbage_collect(mgr: &mut ResourceManager) {
149    let ids_to_unload: Vec<u32> = mgr
150        .resources
151        .iter()
152        .filter(|r| r.ref_count == 0 && r.state == ResourceState::Loaded)
153        .map(|r| r.id)
154        .collect();
155    for id in ids_to_unload {
156        unload_resource(mgr, id);
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_new_resource_manager() {
166        let mgr = new_resource_manager();
167        assert!(mgr.resources.is_empty());
168        assert_eq!(mgr.total_loaded_bytes, 0);
169    }
170
171    #[test]
172    fn test_register_resource() {
173        let mut mgr = new_resource_manager();
174        let id = register_resource(&mut mgr, "texture/grass", "texture");
175        assert_eq!(id, 1);
176        let r = get_resource(&mgr, id).expect("should succeed");
177        assert_eq!(r.state, ResourceState::Unloaded);
178        assert_eq!(r.key, "texture/grass");
179    }
180
181    #[test]
182    fn test_load_resource() {
183        let mut mgr = new_resource_manager();
184        let id = register_resource(&mut mgr, "mesh/head", "mesh");
185        load_resource(&mut mgr, id, vec![1u8; 1024]);
186        let r = get_resource(&mgr, id).expect("should succeed");
187        assert_eq!(r.state, ResourceState::Loaded);
188        assert_eq!(r.size_bytes, 1024);
189        assert_eq!(total_memory(&mgr), 1024);
190    }
191
192    #[test]
193    fn test_unload_resource() {
194        let mut mgr = new_resource_manager();
195        let id = register_resource(&mut mgr, "a", "t");
196        load_resource(&mut mgr, id, vec![0u8; 512]);
197        unload_resource(&mut mgr, id);
198        let r = get_resource(&mgr, id).expect("should succeed");
199        assert_eq!(r.state, ResourceState::Unloaded);
200        assert_eq!(total_memory(&mgr), 0);
201    }
202
203    #[test]
204    fn test_fail_resource() {
205        let mut mgr = new_resource_manager();
206        let id = register_resource(&mut mgr, "b", "t");
207        fail_resource(&mut mgr, id, "file not found");
208        let r = get_resource(&mgr, id).expect("should succeed");
209        assert!(matches!(r.state, ResourceState::Failed(_)));
210    }
211
212    #[test]
213    fn test_retain_release_auto_unload() {
214        let mut mgr = new_resource_manager();
215        let id = register_resource(&mut mgr, "c", "t");
216        load_resource(&mut mgr, id, vec![1u8; 256]);
217        retain_resource(&mut mgr, id);
218        assert_eq!(get_resource(&mgr, id).expect("should succeed").ref_count, 1);
219        release_resource(&mut mgr, id);
220        let r = get_resource(&mgr, id).expect("should succeed");
221        assert_eq!(r.state, ResourceState::Unloaded);
222        assert_eq!(total_memory(&mgr), 0);
223    }
224
225    #[test]
226    fn test_total_memory_multiple() {
227        let mut mgr = new_resource_manager();
228        let id1 = register_resource(&mut mgr, "r1", "t");
229        let id2 = register_resource(&mut mgr, "r2", "t");
230        load_resource(&mut mgr, id1, vec![0u8; 100]);
231        load_resource(&mut mgr, id2, vec![0u8; 200]);
232        assert_eq!(total_memory(&mgr), 300);
233    }
234
235    #[test]
236    fn test_garbage_collect() {
237        let mut mgr = new_resource_manager();
238        let id1 = register_resource(&mut mgr, "g1", "t");
239        let id2 = register_resource(&mut mgr, "g2", "t");
240        load_resource(&mut mgr, id1, vec![0u8; 50]);
241        load_resource(&mut mgr, id2, vec![0u8; 50]);
242        retain_resource(&mut mgr, id1);
243        // id2 has ref_count=0, should be GC'd
244        garbage_collect(&mut mgr);
245        assert_eq!(
246            get_resource(&mgr, id2).expect("should succeed").state,
247            ResourceState::Unloaded
248        );
249        assert_eq!(
250            get_resource(&mgr, id1).expect("should succeed").state,
251            ResourceState::Loaded
252        );
253    }
254
255    #[test]
256    fn test_get_by_key() {
257        let mut mgr = new_resource_manager();
258        let id = register_resource(&mut mgr, "unique/key", "mesh");
259        load_resource(&mut mgr, id, vec![1u8; 32]);
260        let r = get_by_key(&mgr, "unique/key").expect("should succeed");
261        assert_eq!(r.id, id);
262    }
263
264    #[test]
265    fn test_loaded_count() {
266        let mut mgr = new_resource_manager();
267        let id1 = register_resource(&mut mgr, "k1", "t");
268        let id2 = register_resource(&mut mgr, "k2", "t");
269        register_resource(&mut mgr, "k3", "t");
270        load_resource(&mut mgr, id1, vec![0u8; 10]);
271        load_resource(&mut mgr, id2, vec![0u8; 10]);
272        assert_eq!(loaded_count(&mgr), 2);
273    }
274
275    #[test]
276    fn test_failed_count() {
277        let mut mgr = new_resource_manager();
278        let id1 = register_resource(&mut mgr, "f1", "t");
279        let id2 = register_resource(&mut mgr, "f2", "t");
280        register_resource(&mut mgr, "f3", "t");
281        fail_resource(&mut mgr, id1, "err");
282        fail_resource(&mut mgr, id2, "err2");
283        assert_eq!(failed_count(&mgr), 2);
284    }
285
286    #[test]
287    fn test_ids_increment() {
288        let mut mgr = new_resource_manager();
289        let id1 = register_resource(&mut mgr, "a", "t");
290        let id2 = register_resource(&mut mgr, "b", "t");
291        assert!(id2 > id1);
292    }
293
294    #[test]
295    fn test_retain_multiple_refs() {
296        let mut mgr = new_resource_manager();
297        let id = register_resource(&mut mgr, "multi", "t");
298        load_resource(&mut mgr, id, vec![0u8; 100]);
299        retain_resource(&mut mgr, id);
300        retain_resource(&mut mgr, id);
301        release_resource(&mut mgr, id);
302        // still loaded (ref_count = 1)
303        assert_eq!(
304            get_resource(&mgr, id).expect("should succeed").state,
305            ResourceState::Loaded
306        );
307        release_resource(&mut mgr, id);
308        // now unloaded
309        assert_eq!(
310            get_resource(&mgr, id).expect("should succeed").state,
311            ResourceState::Unloaded
312        );
313    }
314}