hypen_engine/lifecycle/
resource.rs

1use indexmap::IndexMap;
2use std::sync::Arc;
3
4/// A cached resource (image, font, etc.)
5#[derive(Clone)]
6pub struct Resource {
7    /// Resource data (platform-specific)
8    pub data: Arc<Vec<u8>>,
9
10    /// MIME type
11    pub mime_type: String,
12
13    /// Optional fingerprint/hash for cache validation
14    pub fingerprint: Option<String>,
15}
16
17impl Resource {
18    pub fn new(data: Vec<u8>, mime_type: impl Into<String>) -> Self {
19        Self {
20            data: Arc::new(data),
21            mime_type: mime_type.into(),
22            fingerprint: None,
23        }
24    }
25
26    pub fn with_fingerprint(mut self, fingerprint: impl Into<String>) -> Self {
27        self.fingerprint = Some(fingerprint.into());
28        self
29    }
30}
31
32/// Callback for fetching resources (abstracted so platforms can override)
33pub type ResourceFetcher = Box<dyn Fn(&str) -> Option<Resource> + Send + Sync>;
34
35/// Cache for resources (images, fonts, etc.)
36pub struct ResourceCache {
37    /// Cached resources by URL/key
38    cache: IndexMap<String, Resource>,
39
40    /// Fetcher function (can be overridden by platform)
41    fetcher: Option<ResourceFetcher>,
42}
43
44impl ResourceCache {
45    pub fn new() -> Self {
46        Self {
47            cache: IndexMap::new(),
48            fetcher: None,
49        }
50    }
51
52    /// Set a custom resource fetcher
53    pub fn set_fetcher<F>(&mut self, fetcher: F)
54    where
55        F: Fn(&str) -> Option<Resource> + Send + Sync + 'static,
56    {
57        self.fetcher = Some(Box::new(fetcher));
58    }
59
60    /// Get a resource by URL/key
61    pub fn get(&mut self, url: &str) -> Option<Resource> {
62        // Check cache first
63        if let Some(resource) = self.cache.get(url) {
64            return Some(resource.clone());
65        }
66
67        // Try to fetch using the fetcher
68        if let Some(ref fetcher) = self.fetcher {
69            if let Some(resource) = fetcher(url) {
70                self.cache.insert(url.to_string(), resource.clone());
71                return Some(resource);
72            }
73        }
74
75        None
76    }
77
78    /// Manually insert a resource into the cache
79    pub fn insert(&mut self, url: impl Into<String>, resource: Resource) {
80        self.cache.insert(url.into(), resource);
81    }
82
83    /// Clear the cache
84    pub fn clear(&mut self) {
85        self.cache.clear();
86    }
87
88    /// Remove a specific resource
89    pub fn remove(&mut self, url: &str) -> Option<Resource> {
90        self.cache.shift_remove(url)
91    }
92
93    /// Get cache size (number of entries)
94    pub fn size(&self) -> usize {
95        self.cache.len()
96    }
97}
98
99impl Default for ResourceCache {
100    fn default() -> Self {
101        Self::new()
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_cache_insert_get() {
111        let mut cache = ResourceCache::new();
112        let resource = Resource::new(vec![1, 2, 3], "image/png");
113
114        cache.insert("test.png", resource.clone());
115
116        let retrieved = cache.get("test.png").unwrap();
117        assert_eq!(*retrieved.data, vec![1, 2, 3]);
118        assert_eq!(retrieved.mime_type, "image/png");
119    }
120
121    #[test]
122    fn test_custom_fetcher() {
123        let mut cache = ResourceCache::new();
124
125        cache.set_fetcher(|url| {
126            if url == "auto.png" {
127                Some(Resource::new(vec![4, 5, 6], "image/png"))
128            } else {
129                None
130            }
131        });
132
133        let resource = cache.get("auto.png").unwrap();
134        assert_eq!(*resource.data, vec![4, 5, 6]);
135
136        // Should be cached now
137        assert_eq!(cache.size(), 1);
138    }
139}