hassium_core/assets/
database.rs

1use crate::{
2    assets::{
3        asset::{Asset, AssetID},
4        protocol::{AssetLoadResult, AssetProtocol, AssetVariant, Meta},
5    },
6    fetch::{FetchEngine, FetchProcessReader, FetchStatus},
7};
8use std::{borrow::BorrowMut, collections::HashMap, mem::replace};
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum LoadStatus {
12    InvalidPath(String),
13    UnknownProtocol(String),
14    FetchError(FetchStatus),
15}
16
17pub struct AssetsDatabase {
18    fetch_engines: Vec<Box<FetchEngine>>,
19    protocols: HashMap<String, Box<AssetProtocol>>,
20    assets: HashMap<AssetID, (String, Asset)>,
21    table: HashMap<String, AssetID>,
22    loading: HashMap<String, (String, Box<FetchProcessReader>)>,
23    #[allow(clippy::type_complexity)]
24    yielded: HashMap<String, (String, Meta, Vec<(String, String)>)>,
25    lately_loaded: Vec<(String, AssetID)>,
26    lately_unloaded: Vec<(String, AssetID)>,
27}
28
29impl AssetsDatabase {
30    pub fn new<FE>(fetch_engine: FE) -> Self
31    where
32        FE: FetchEngine + 'static,
33    {
34        Self {
35            fetch_engines: vec![Box::new(fetch_engine)],
36            protocols: Default::default(),
37            assets: Default::default(),
38            table: Default::default(),
39            loading: Default::default(),
40            yielded: Default::default(),
41            lately_loaded: vec![],
42            lately_unloaded: vec![],
43        }
44    }
45
46    pub fn loaded_count(&self) -> usize {
47        self.assets.len()
48    }
49
50    pub fn loaded_paths(&self) -> Vec<String> {
51        self.assets
52            .iter()
53            .map(|(_, (_, a))| a.to_full_path())
54            .collect()
55    }
56
57    pub fn loaded_ids(&self) -> Vec<AssetID> {
58        self.assets.iter().map(|(id, _)| *id).collect()
59    }
60
61    pub fn loading_count(&self) -> usize {
62        self.loading.len()
63    }
64
65    pub fn loading_paths(&self) -> Vec<String> {
66        let mut result = self
67            .loading
68            .iter()
69            .map(|(path, (prot, _))| format!("{}://{}", prot, path))
70            .collect::<Vec<_>>();
71        result.sort();
72        result
73    }
74
75    pub fn yielded_count(&self) -> usize {
76        self.yielded.len()
77    }
78
79    pub fn yielded_paths(&self) -> Vec<String> {
80        let mut result = self
81            .yielded
82            .iter()
83            .map(|(path, (prot, _, _))| format!("{}://{}", prot, path))
84            .collect::<Vec<_>>();
85        result.sort();
86        result
87    }
88
89    pub fn yielded_deps_count(&self) -> usize {
90        self.yielded
91            .iter()
92            .map(|(_, (_, _, list))| list.len())
93            .sum()
94    }
95
96    pub fn yielded_deps_paths(&self) -> Vec<String> {
97        let mut result = self
98            .yielded
99            .iter()
100            .flat_map(|(_, (_, _, list))| {
101                list.iter().map(|(p, _)| p.to_owned()).collect::<Vec<_>>()
102            })
103            .collect::<Vec<_>>();
104        result.sort();
105        result.dedup();
106        result
107    }
108
109    pub fn lately_loaded(&self) -> impl Iterator<Item = &AssetID> {
110        self.lately_loaded.iter().map(|(_, id)| id)
111    }
112
113    pub fn lately_loaded_protocol<'a>(
114        &'a self,
115        protocol: &'a str,
116    ) -> impl Iterator<Item = &'a AssetID> {
117        self.lately_loaded
118            .iter()
119            .filter_map(move |(prot, id)| if protocol == prot { Some(id) } else { None })
120    }
121
122    pub fn lately_unloaded(&self) -> impl Iterator<Item = &AssetID> {
123        self.lately_unloaded.iter().map(|(_, id)| id)
124    }
125
126    pub fn lately_unloaded_protocol<'a>(
127        &'a self,
128        protocol: &'a str,
129    ) -> impl Iterator<Item = &'a AssetID> {
130        self.lately_unloaded
131            .iter()
132            .filter_map(move |(prot, id)| if protocol == prot { Some(id) } else { None })
133    }
134
135    pub fn is_ready(&self) -> bool {
136        self.loading.is_empty() && self.yielded.is_empty()
137    }
138
139    pub fn are_ready<I, S>(&self, iter: I) -> bool
140    where
141        I: IntoIterator<Item = S>,
142        S: AsRef<str>,
143    {
144        !iter
145            .into_iter()
146            .any(|path| !self.table.contains_key(path.as_ref()))
147    }
148
149    pub fn push_fetch_engine(&mut self, fetch_engine: Box<FetchEngine>) {
150        self.fetch_engines.push(fetch_engine);
151    }
152
153    pub fn pop_fetch_engine(&mut self) -> Option<Box<FetchEngine>> {
154        if !self.fetch_engines.is_empty() {
155            self.fetch_engines.pop()
156        } else {
157            None
158        }
159    }
160
161    #[allow(clippy::borrowed_box)]
162    pub fn fetch_engine(&self) -> &Box<dyn FetchEngine> {
163        self.fetch_engines.last().unwrap()
164    }
165
166    #[allow(clippy::borrowed_box)]
167    pub fn fetch_engine_mut(&mut self) -> &mut Box<dyn FetchEngine> {
168        self.fetch_engines.last_mut().unwrap()
169    }
170
171    pub fn with_fetch_engine<F, R>(&mut self, mut action: F) -> R
172    where
173        F: FnMut(&mut FetchEngine) -> R,
174    {
175        let fetch_engine: &mut FetchEngine = self.fetch_engine_mut().borrow_mut();
176        action(fetch_engine)
177    }
178
179    pub fn register<FE>(&mut self, mut protocol: FE)
180    where
181        FE: AssetProtocol + 'static,
182    {
183        protocol.on_register();
184        let name = protocol.name().to_owned();
185        self.protocols.insert(name, Box::new(protocol));
186    }
187
188    pub fn unregister(&mut self, protocol_name: &str) -> Option<Box<AssetProtocol>> {
189        if let Some(mut protocol) = self.protocols.remove(protocol_name) {
190            protocol.on_unregister();
191            Some(protocol)
192        } else {
193            None
194        }
195    }
196
197    pub fn load(&mut self, path: &str) -> Result<(), LoadStatus> {
198        if self.table.contains_key(path) {
199            return Ok(());
200        }
201        let parts = path.split("://").take(2).collect::<Vec<_>>();
202        if parts.len() == 2 {
203            let prot = parts[0];
204            let subpath = parts[1];
205            if self.protocols.contains_key(prot) {
206                let reader = self.fetch_engine_mut().fetch(subpath);
207                match reader {
208                    Ok(reader) => {
209                        self.loading
210                            .insert(subpath.to_owned(), (prot.to_owned(), reader));
211                        Ok(())
212                    }
213                    Err(status) => Err(LoadStatus::FetchError(status)),
214                }
215            } else {
216                Err(LoadStatus::UnknownProtocol(prot.to_owned()))
217            }
218        } else {
219            Err(LoadStatus::InvalidPath(path.to_owned()))
220        }
221    }
222
223    pub fn insert(&mut self, path: &str, asset: Asset) -> AssetID {
224        let id = asset.id();
225        self.lately_loaded.push((asset.protocol().to_owned(), id));
226        self.assets.insert(id, (path.to_owned(), asset));
227        self.table.insert(path.to_owned(), id);
228        id
229    }
230
231    pub fn remove_by_id(&mut self, id: AssetID) -> Option<Asset> {
232        if let Some((path, asset)) = self.assets.remove(&id) {
233            self.table.remove(&path);
234            self.lately_unloaded.push((asset.protocol().to_owned(), id));
235            if let Some(protocol) = self.protocols.get_mut(asset.protocol()) {
236                if let Some(list) = protocol.on_unload(&asset) {
237                    self.remove_by_variants(&list);
238                }
239            }
240            Some(asset)
241        } else {
242            None
243        }
244    }
245
246    pub fn remove_by_path(&mut self, path: &str) -> Option<Asset> {
247        if let Some(id) = self.table.remove(path) {
248            if let Some((_, asset)) = self.assets.remove(&id) {
249                self.lately_unloaded.push((asset.protocol().to_owned(), id));
250                if let Some(protocol) = self.protocols.get_mut(asset.protocol()) {
251                    if let Some(list) = protocol.on_unload(&asset) {
252                        self.remove_by_variants(&list);
253                    }
254                }
255                return Some(asset);
256            }
257        }
258        None
259    }
260
261    pub fn remove_by_variants(&mut self, variants: &[AssetVariant]) {
262        for v in variants {
263            match v {
264                AssetVariant::Id(id) => self.remove_by_id(*id),
265                AssetVariant::Path(path) => self.remove_by_path(path),
266            };
267        }
268    }
269
270    pub fn id_by_path(&self, path: &str) -> Option<AssetID> {
271        self.table.get(path).cloned()
272    }
273
274    pub fn path_by_id(&self, id: AssetID) -> Option<&str> {
275        self.assets.get(&id).map(|(path, _)| path.as_str())
276    }
277
278    pub fn asset_by_id(&self, id: AssetID) -> Option<&Asset> {
279        self.assets.get(&id).map(|(_, asset)| asset)
280    }
281
282    pub fn asset_by_path(&self, path: &str) -> Option<&Asset> {
283        if let Some(id) = self.table.get(path) {
284            if let Some((_, asset)) = self.assets.get(id) {
285                return Some(asset);
286            }
287        }
288        None
289    }
290
291    pub fn process(&mut self) {
292        self.lately_loaded.clear();
293        self.lately_unloaded.clear();
294        loop {
295            let to_dispatch = {
296                self.loading
297                    .iter()
298                    .filter_map(|(path, (prot, reader))| {
299                        if let Some(data) = reader.read() {
300                            Some((path.to_owned(), prot.to_owned(), data))
301                        } else {
302                            None
303                        }
304                    })
305                    .collect::<Vec<_>>()
306            };
307            for (path, prot, data) in to_dispatch {
308                if let Some(protocol) = self.protocols.get_mut(&prot) {
309                    match protocol.on_load(data) {
310                        AssetLoadResult::Data(data) => {
311                            let asset = Asset::new(&prot, &path, data);
312                            self.insert(&asset.to_full_path(), asset);
313                        }
314                        AssetLoadResult::Yield(meta, list) => {
315                            let list = list
316                                .into_iter()
317                                .filter(|(_, path)| self.load(&path).is_ok())
318                                .collect();
319                            self.yielded.insert(path, (prot, meta, list));
320                        }
321                        _ => {}
322                    }
323                }
324            }
325            if !self
326                .loading
327                .iter()
328                .any(|(_, (_, reader))| reader.status() == FetchStatus::Done)
329            {
330                break;
331            }
332        }
333        self.loading.retain(|_, (_, reader)| {
334            if let FetchStatus::InProgress(_) = reader.status() {
335                true
336            } else {
337                false
338            }
339        });
340        let yielded = replace(&mut self.yielded, Default::default());
341        for (path, (prot, meta, list)) in yielded {
342            if list.iter().all(|(_, path)| self.table.contains_key(path)) {
343                let ptr = self as *const Self;
344                if let Some(protocol) = self.protocols.get_mut(&prot) {
345                    let list = list
346                        .iter()
347                        .map(|(key, path)| unsafe {
348                            let asset = &(*ptr).table[path];
349                            let asset = &(*ptr).assets[asset].1;
350                            (key.as_str(), asset)
351                        })
352                        .collect::<Vec<_>>();
353                    match protocol.on_resume(meta, &list) {
354                        AssetLoadResult::Data(data) => {
355                            let asset = Asset::new(&prot, &path, data);
356                            self.insert(&asset.to_full_path(), asset);
357                        }
358                        AssetLoadResult::Yield(meta, list) => {
359                            let list = list
360                                .into_iter()
361                                .filter(|(_, path)| self.load(&path).is_ok())
362                                .collect();
363                            self.yielded.insert(path, (prot, meta, list));
364                        }
365                        _ => {}
366                    }
367                }
368            } else {
369                self.yielded.insert(path, (prot, meta, list));
370            }
371        }
372    }
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378    use crate::assets::protocols::prelude::*;
379    use crate::fetch::*;
380
381    #[test]
382    fn test_general() {
383        let mut fetch_engine = engines::map::MapFetchEngine::default();
384        fetch_engine.map.insert(
385            "assets.txt".to_owned(),
386            br#"
387                txt://a.txt
388                txt://b.txt
389            "#
390            .to_vec(),
391        );
392        fetch_engine.map.insert("a.txt".to_owned(), b"A".to_vec());
393        fetch_engine.map.insert("b.txt".to_owned(), b"B".to_vec());
394
395        let mut database = AssetsDatabase::new(fetch_engine);
396        database.register(TextAssetProtocol);
397        database.register(SetAssetProtocol);
398        assert_eq!(database.load("set://assets.txt"), Ok(()));
399        assert_eq!(database.loaded_count(), 0);
400        assert_eq!(database.loading_count(), 1);
401        assert_eq!(database.yielded_count(), 0);
402        assert_eq!(database.yielded_deps_count(), 0);
403
404        database.process();
405        assert_eq!(database.loaded_count(), 3);
406        assert_eq!(database.loading_count(), 0);
407        assert_eq!(database.yielded_count(), 0);
408        assert_eq!(database.yielded_deps_count(), 0);
409
410        assert!(database.asset_by_path("set://assets.txt").is_some());
411        assert_eq!(
412            &database
413                .asset_by_path("set://assets.txt")
414                .unwrap()
415                .to_full_path(),
416            "set://assets.txt"
417        );
418        assert!(database
419            .asset_by_path("set://assets.txt")
420            .unwrap()
421            .is::<SetAsset>());
422        assert_eq!(
423            database
424                .asset_by_path("set://assets.txt")
425                .unwrap()
426                .get::<SetAsset>()
427                .unwrap()
428                .paths()
429                .to_vec(),
430            vec!["txt://a.txt".to_owned(), "txt://b.txt".to_owned()],
431        );
432
433        assert!(database.asset_by_path("txt://a.txt").is_some());
434        assert!(database
435            .asset_by_path("txt://a.txt")
436            .unwrap()
437            .is::<TextAsset>());
438        assert_eq!(
439            database
440                .asset_by_path("txt://a.txt")
441                .unwrap()
442                .get::<TextAsset>()
443                .unwrap()
444                .get(),
445            "A"
446        );
447
448        assert!(database.asset_by_path("txt://b.txt").is_some());
449        assert_eq!(
450            database
451                .asset_by_path("txt://b.txt")
452                .unwrap()
453                .get::<TextAsset>()
454                .unwrap()
455                .get(),
456            "B"
457        );
458
459        assert!(database.remove_by_path("set://assets.txt").is_some());
460        assert_eq!(database.loaded_count(), 0);
461        assert_eq!(database.loading_count(), 0);
462        assert_eq!(database.yielded_count(), 0);
463        assert_eq!(database.yielded_deps_count(), 0);
464    }
465}