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