Skip to main content

euv_engine/asset/
impl.rs

1use super::*;
2
3/// Implements cache query and management for `AssetCache`.
4impl AssetCache {
5    /// Returns the state of the asset with the given URL, or `None` if not cached.
6    ///
7    /// # Arguments
8    ///
9    /// - `U: AsRef<str>` - The asset URL.
10    ///
11    /// # Returns
12    ///
13    /// - `Option<AssetState>` - The asset state, or `None`.
14    pub fn get_state<U>(&self, url: U) -> Option<AssetState>
15    where
16        U: AsRef<str>,
17    {
18        self.get_entries()
19            .get(url.as_ref())
20            .map(|entry: &AssetEntry| entry.get_state())
21    }
22
23    /// Returns the loaded image for the given URL, or `None` if not loaded.
24    ///
25    /// # Arguments
26    ///
27    /// - `U: AsRef<str>` - The asset URL.
28    ///
29    /// # Returns
30    ///
31    /// - `Option<HtmlImageElement>` - The loaded image, or `None`.
32    pub fn get_image<U>(&self, url: U) -> Option<HtmlImageElement>
33    where
34        U: AsRef<str>,
35    {
36        let entry: &AssetEntry = self.get_entries().get(url.as_ref())?;
37        if entry.get_state() != AssetState::Loaded {
38            return None;
39        }
40        entry.get_image()
41    }
42
43    /// Returns `true` if all assets in the cache have finished loading.
44    ///
45    /// # Returns
46    ///
47    /// - `bool` - True if no assets are in the `Loading` state.
48    pub fn is_all_loaded(&self) -> bool {
49        self.get_entries()
50            .values()
51            .all(|entry: &AssetEntry| entry.get_state() != AssetState::Loading)
52    }
53
54    /// Returns the number of assets that have been successfully loaded.
55    ///
56    /// # Returns
57    ///
58    /// - `usize` - The count of loaded assets.
59    pub fn loaded_count(&self) -> usize {
60        self.get_entries()
61            .values()
62            .filter(|entry: &&AssetEntry| entry.get_state() == AssetState::Loaded)
63            .count()
64    }
65
66    /// Removes all entries from the cache.
67    pub fn clear(&mut self) {
68        self.get_mut_entries().clear();
69    }
70}
71
72/// Implements `Default` for `AssetCache` as a new empty cache.
73impl Default for AssetCache {
74    /// Constructs a default [`AssetCache`] value.
75    ///
76    /// # Returns
77    ///
78    /// - `AssetCache` - A default-constructed instance with the documented initial state.
79    fn default() -> AssetCache {
80        AssetCache::new()
81    }
82}
83
84/// Implements asynchronous asset loading for `AssetLoader`.
85impl AssetLoader {
86    /// Begins loading an image asset from the given URL.
87    ///
88    /// Creates an `HtmlImageElement`, sets its `src`, and registers `onload`/`onerror`
89    /// callbacks to update the shared cache state. The image loads asynchronously.
90    ///
91    /// # Arguments
92    ///
93    /// - `String` - The URL of the image to load.
94    pub fn load_image(&mut self, url: String) {
95        let Ok(image) = HtmlImageElement::new() else {
96            return;
97        };
98        let entry: AssetEntry = AssetEntry::new(
99            AssetType::Image,
100            AssetState::Loading,
101            Some(image.clone()),
102            url.clone(),
103        );
104        self.get_cache()
105            .get_mut()
106            .get_mut_entries()
107            .insert(url.clone(), entry);
108        *self.get_mut_pending_count() += 1;
109        let cache_clone: Rc<EngineCell<AssetCache>> = self.get_cache().clone();
110        let url_for_onload: String = url.clone();
111        let url_for_onerror: String = url.clone();
112        let onload_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
113            let cache_ref: &mut AssetCache = cache_clone.get_mut();
114            if let Some(mut entry) = cache_ref.get_entries().get(&url_for_onload).cloned() {
115                entry.set_state(AssetState::Loaded);
116                cache_ref
117                    .get_mut_entries()
118                    .insert(url_for_onload.clone(), entry);
119            }
120        }));
121        let cache_clone_err: Rc<EngineCell<AssetCache>> = self.get_cache().clone();
122        let onerror_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
123            let cache_ref: &mut AssetCache = cache_clone_err.get_mut();
124            if let Some(mut entry) = cache_ref.get_entries().get(&url_for_onerror).cloned() {
125                entry.set_state(AssetState::Error);
126                cache_ref
127                    .get_mut_entries()
128                    .insert(url_for_onerror.clone(), entry);
129            }
130        }));
131        image.set_onload(Some(onload_closure.as_ref().unchecked_ref()));
132        image.set_onerror(Some(onerror_closure.as_ref().unchecked_ref()));
133        image.set_src(&url);
134        self.get_closures().get_mut().push(onload_closure);
135        self.get_closures().get_mut().push(onerror_closure);
136    }
137
138    /// Returns whether all requested assets have finished loading.
139    ///
140    /// # Returns
141    ///
142    /// - `bool` - True if no assets are pending.
143    pub fn is_all_loaded(&self) -> bool {
144        self.get_cache().get().is_all_loaded()
145    }
146
147    /// Returns the loaded image for the given URL.
148    ///
149    /// # Arguments
150    ///
151    /// - `U: AsRef<str>` - The asset URL.
152    ///
153    /// # Returns
154    ///
155    /// - `Option<HtmlImageElement>` - The loaded image, or `None`.
156    pub fn get_image<U>(&self, url: U) -> Option<HtmlImageElement>
157    where
158        U: AsRef<str>,
159    {
160        self.get_cache().get().get_image(url.as_ref())
161    }
162
163    /// Returns the progress ratio of loaded assets.
164    ///
165    /// # Returns
166    ///
167    /// - `f64` - The ratio in the range 0.0 to 1.0.
168    pub fn progress(&self) -> f64 {
169        let cache_ref: &AssetCache = self.get_cache().get();
170        let total: usize = cache_ref.get_entries().len();
171        if total == 0 {
172            return 1.0;
173        }
174        cache_ref.loaded_count() as f64 / total as f64
175    }
176}
177
178/// Implements `Default` for `AssetLoader` as a new empty loader.
179impl Default for AssetLoader {
180    /// Constructs a default [`AssetLoader`] value.
181    ///
182    /// # Returns
183    ///
184    /// - `AssetLoader` - A default-constructed instance with the documented initial state.
185    fn default() -> AssetLoader {
186        AssetLoader::new()
187    }
188}
189
190/// Implements static asset creation utilities for `AssetLoader`.
191impl AssetLoader {
192    /// Creates an `HtmlImageElement` from the given URL without caching.
193    ///
194    /// The image loads asynchronously. Returns immediately with the image element
195    /// whose `src` is set but may not have finished loading yet.
196    ///
197    /// # Arguments
198    ///
199    /// - `U: AsRef<str>` - The image URL.
200    ///
201    /// # Returns
202    ///
203    /// - `Option<HtmlImageElement>` - The image element, or `None` if creation failed.
204    pub fn create_image_element<U>(url: U) -> Option<HtmlImageElement>
205    where
206        U: AsRef<str>,
207    {
208        let image: HtmlImageElement = HtmlImageElement::new().ok()?;
209        image.set_src(url.as_ref());
210        Some(image)
211    }
212}