1use std::any::{Any, TypeId};
4use std::collections::HashMap;
5use std::sync::Arc;
6
7use crate::error::{AssetError, AssetResult};
8use crate::source::AssetSource;
9
10pub struct LoadContext<'a> {
12 pub source: &'a AssetSource,
14 pub bytes: &'a [u8],
16 pub extension: Option<&'a str>,
18}
19
20impl<'a> LoadContext<'a> {
21 pub fn new(source: &'a AssetSource, bytes: &'a [u8], extension: Option<&'a str>) -> Self {
23 Self {
24 source,
25 bytes,
26 extension,
27 }
28 }
29}
30
31pub const DEFAULT_LOADER_PRIORITY: i32 = 0;
33
34pub trait AssetLoader: Send + Sync + 'static {
56 type Asset: Send + Sync + 'static;
58
59 fn extensions(&self) -> &[&str];
63
64 fn load(&self, ctx: LoadContext<'_>) -> AssetResult<Self::Asset>;
66
67 fn priority(&self) -> i32 {
72 DEFAULT_LOADER_PRIORITY
73 }
74}
75
76pub trait ErasedAssetLoader: Send + Sync {
78 fn asset_type_id(&self) -> TypeId;
80
81 fn asset_type_name(&self) -> &'static str;
83
84 fn extensions(&self) -> &[&str];
86
87 fn priority(&self) -> i32;
89
90 fn load_erased(&self, ctx: LoadContext<'_>) -> AssetResult<Box<dyn Any + Send + Sync>>;
92}
93
94impl<L: AssetLoader> ErasedAssetLoader for L
95where
96 L::Asset: crate::Asset,
97{
98 fn asset_type_id(&self) -> TypeId {
99 TypeId::of::<L::Asset>()
100 }
101
102 fn asset_type_name(&self) -> &'static str {
103 <L::Asset as crate::Asset>::type_name()
104 }
105
106 fn extensions(&self) -> &[&str] {
107 AssetLoader::extensions(self)
108 }
109
110 fn priority(&self) -> i32 {
111 AssetLoader::priority(self)
112 }
113
114 fn load_erased(&self, ctx: LoadContext<'_>) -> AssetResult<Box<dyn Any + Send + Sync>> {
115 let asset = self.load(ctx)?;
116 Ok(Box::new(asset))
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122struct LoaderKey {
123 type_id: TypeId,
124 extension: String,
125}
126
127struct LoaderEntry {
129 loader: Arc<dyn ErasedAssetLoader>,
130 priority: i32,
131}
132
133#[derive(Default)]
142pub struct LoaderRegistry {
143 by_type_and_ext: HashMap<LoaderKey, Vec<LoaderEntry>>,
145 by_type: HashMap<TypeId, Vec<Arc<dyn ErasedAssetLoader>>>,
147 by_extension: HashMap<String, Vec<LoaderEntry>>,
149}
150
151impl LoaderRegistry {
152 pub fn new() -> Self {
154 Self::default()
155 }
156
157 pub fn register<L: AssetLoader>(&mut self, loader: L)
162 where
163 L::Asset: crate::Asset,
164 {
165 let loader = Arc::new(loader);
166 let type_id = loader.asset_type_id();
167 let priority = loader.priority();
168
169 for ext in loader.extensions() {
171 let ext_lower = ext.to_lowercase();
172
173 let key = LoaderKey {
175 type_id,
176 extension: ext_lower.clone(),
177 };
178
179 let entries = self.by_type_and_ext.entry(key).or_default();
180 entries.push(LoaderEntry {
181 loader: loader.clone(),
182 priority,
183 });
184 entries.sort_by(|a, b| b.priority.cmp(&a.priority));
186
187 let ext_entries = self.by_extension.entry(ext_lower).or_default();
189 ext_entries.push(LoaderEntry {
190 loader: loader.clone(),
191 priority,
192 });
193 ext_entries.sort_by(|a, b| b.priority.cmp(&a.priority));
194 }
195
196 self.by_type.entry(type_id).or_default().push(loader);
198 }
199
200 pub fn get_for_type_and_extension<T: 'static>(
204 &self,
205 extension: &str,
206 ) -> Option<&Arc<dyn ErasedAssetLoader>> {
207 let key = LoaderKey {
208 type_id: TypeId::of::<T>(),
209 extension: extension.to_lowercase(),
210 };
211
212 self.by_type_and_ext
213 .get(&key)
214 .and_then(|entries| entries.first())
215 .map(|entry| &entry.loader)
216 }
217
218 pub fn get_by_type<T: 'static>(&self) -> Option<&[Arc<dyn ErasedAssetLoader>]> {
220 self.by_type.get(&TypeId::of::<T>()).map(|v| v.as_slice())
221 }
222
223 pub fn get_by_extension(&self, extension: &str) -> Option<&Arc<dyn ErasedAssetLoader>> {
225 let ext_lower = extension.to_lowercase();
226 self.by_extension
227 .get(&ext_lower)
228 .and_then(|entries| entries.first())
229 .map(|entry| &entry.loader)
230 }
231
232 pub fn has_loader_for<T: 'static>(&self, extension: &str) -> bool {
234 let key = LoaderKey {
235 type_id: TypeId::of::<T>(),
236 extension: extension.to_lowercase(),
237 };
238 self.by_type_and_ext.contains_key(&key)
239 }
240
241 pub fn has_loader_for_type<T: 'static>(&self) -> bool {
243 self.by_type.contains_key(&TypeId::of::<T>())
244 }
245
246 pub fn has_loader_for_extension(&self, extension: &str) -> bool {
248 let ext_lower = extension.to_lowercase();
249 self.by_extension.contains_key(&ext_lower)
250 }
251
252 pub fn load_typed<T: crate::Asset>(
260 &self,
261 source: &AssetSource,
262 bytes: &[u8],
263 extension: Option<&str>,
264 ) -> AssetResult<T> {
265 let ext = extension.ok_or_else(|| AssetError::NoLoaderForExtension {
266 extension: "<none>".to_string(),
267 })?;
268
269 let loader =
270 self.get_for_type_and_extension::<T>(ext)
271 .ok_or_else(|| AssetError::NoLoader {
272 type_id: TypeId::of::<T>(),
273 type_name: Some(T::type_name()),
274 })?;
275
276 let ctx = LoadContext::new(source, bytes, Some(ext));
277 let boxed = loader.load_erased(ctx)?;
278
279 boxed
281 .downcast::<T>()
282 .map(|b| *b)
283 .map_err(|_| AssetError::TypeMismatch {
284 expected: T::type_name(),
285 actual: TypeId::of::<T>(),
286 })
287 }
288
289 pub fn load(
294 &self,
295 source: &AssetSource,
296 bytes: &[u8],
297 extension: Option<&str>,
298 ) -> AssetResult<Box<dyn Any + Send + Sync>> {
299 let ext = extension.ok_or_else(|| AssetError::NoLoaderForExtension {
300 extension: "<none>".to_string(),
301 })?;
302
303 let loader =
304 self.get_by_extension(ext)
305 .ok_or_else(|| AssetError::NoLoaderForExtension {
306 extension: ext.to_string(),
307 })?;
308
309 let ctx = LoadContext::new(source, bytes, Some(ext));
310 loader.load_erased(ctx)
311 }
312
313 pub fn extensions_for_type<T: 'static>(&self) -> Vec<&str> {
315 self.by_type
316 .get(&TypeId::of::<T>())
317 .map(|loaders| {
318 loaders
319 .iter()
320 .flat_map(|l| l.extensions().iter().copied())
321 .collect()
322 })
323 .unwrap_or_default()
324 }
325}
326
327pub struct TextLoader;
329
330impl AssetLoader for TextLoader {
331 type Asset = String;
332
333 fn extensions(&self) -> &[&str] {
334 &["txt", "text", "md", "markdown"]
335 }
336
337 fn load(&self, ctx: LoadContext<'_>) -> AssetResult<Self::Asset> {
338 String::from_utf8(ctx.bytes.to_vec()).map_err(|e| AssetError::LoaderError {
339 path: ctx.source.display_path(),
340 message: format!("Invalid UTF-8: {}", e),
341 })
342 }
343}
344
345pub struct BytesLoader;
347
348impl AssetLoader for BytesLoader {
349 type Asset = Vec<u8>;
350
351 fn extensions(&self) -> &[&str] {
352 &["bin", "bytes", "dat"]
353 }
354
355 fn load(&self, ctx: LoadContext<'_>) -> AssetResult<Self::Asset> {
356 Ok(ctx.bytes.to_vec())
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use crate::Asset;
364
365 #[derive(Debug, PartialEq)]
367 struct TestData {
368 value: i32,
369 }
370
371 impl Asset for TestData {
372 fn type_name() -> &'static str {
373 "TestData"
374 }
375 }
376
377 struct LowPriorityLoader;
379
380 impl AssetLoader for LowPriorityLoader {
381 type Asset = TestData;
382
383 fn extensions(&self) -> &[&str] {
384 &["dat"]
385 }
386
387 fn priority(&self) -> i32 {
388 -10
389 }
390
391 fn load(&self, _ctx: LoadContext<'_>) -> AssetResult<Self::Asset> {
392 Ok(TestData { value: 1 })
393 }
394 }
395
396 struct HighPriorityLoader;
398
399 impl AssetLoader for HighPriorityLoader {
400 type Asset = TestData;
401
402 fn extensions(&self) -> &[&str] {
403 &["dat"]
404 }
405
406 fn priority(&self) -> i32 {
407 10
408 }
409
410 fn load(&self, _ctx: LoadContext<'_>) -> AssetResult<Self::Asset> {
411 Ok(TestData { value: 100 })
412 }
413 }
414
415 #[test]
416 fn test_text_loader() {
417 let loader = TextLoader;
418 let source = AssetSource::memory("test.txt");
419 let bytes = b"Hello, World!";
420 let ctx = LoadContext::new(&source, bytes, Some("txt"));
421
422 let result = loader.load(ctx).unwrap();
423 assert_eq!(result, "Hello, World!");
424 }
425
426 #[test]
427 fn test_bytes_loader() {
428 let loader = BytesLoader;
429 let source = AssetSource::memory("test.bin");
430 let bytes = &[0u8, 1, 2, 3, 4];
431 let ctx = LoadContext::new(&source, bytes, Some("bin"));
432
433 let result = loader.load(ctx).unwrap();
434 assert_eq!(result, vec![0, 1, 2, 3, 4]);
435 }
436
437 #[test]
438 fn test_loader_registry_by_type() {
439 let mut registry = LoaderRegistry::new();
440 registry.register(TextLoader);
441 registry.register(BytesLoader);
442
443 assert!(registry.has_loader_for::<String>("txt"));
445 assert!(registry.has_loader_for::<String>("TXT")); assert!(registry.has_loader_for::<Vec<u8>>("bin"));
449
450 assert!(!registry.has_loader_for::<String>("bin"));
452
453 assert!(!registry.has_loader_for::<Vec<u8>>("txt"));
455
456 assert!(registry.has_loader_for_type::<String>());
457 assert!(registry.has_loader_for_type::<Vec<u8>>());
458 }
459
460 #[test]
461 fn test_loader_priority() {
462 let mut registry = LoaderRegistry::new();
463
464 registry.register(LowPriorityLoader);
466 registry.register(HighPriorityLoader);
467
468 let source = AssetSource::memory("test.dat");
470 let result: TestData = registry.load_typed(&source, b"", Some("dat")).unwrap();
471 assert_eq!(result.value, 100);
472 }
473
474 #[test]
475 fn test_loader_priority_reverse_order() {
476 let mut registry = LoaderRegistry::new();
477
478 registry.register(HighPriorityLoader);
480 registry.register(LowPriorityLoader);
481
482 let source = AssetSource::memory("test.dat");
484 let result: TestData = registry.load_typed(&source, b"", Some("dat")).unwrap();
485 assert_eq!(result.value, 100);
486 }
487
488 #[test]
489 fn test_typed_load() {
490 let mut registry = LoaderRegistry::new();
491 registry.register(TextLoader);
492
493 let source = AssetSource::memory("test.txt");
494 let result: String = registry
495 .load_typed(&source, b"Hello!", Some("txt"))
496 .unwrap();
497 assert_eq!(result, "Hello!");
498 }
499
500 #[test]
501 fn test_no_loader_for_type_extension_combo() {
502 let mut registry = LoaderRegistry::new();
503 registry.register(TextLoader); let source = AssetSource::memory("test.txt");
507 let result: AssetResult<TestData> = registry.load_typed(&source, b"data", Some("txt"));
508 assert!(result.is_err());
509 }
510}