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}