goud_engine/assets/loader/traits.rs
1//! Asset loader traits and type-erased loader wrapper.
2
3use crate::assets::Asset;
4
5use super::{AssetLoadError, LoadContext};
6
7/// Trait for types that can load assets from raw bytes.
8///
9/// Asset loaders are registered with the AssetServer and invoked when
10/// an asset of the corresponding type is requested.
11///
12/// # Type Parameters
13///
14/// - `Asset`: The type of asset this loader produces
15/// - `Settings`: Configuration type for the loader (use `()` if not needed)
16///
17/// # Example
18///
19/// ```ignore
20/// use goud_engine::assets::{Asset, AssetLoader, LoadContext, AssetLoadError, TextAsset, TextAssetLoader};
21///
22/// let loader = TextAssetLoader;
23/// let bytes = b"Hello, World!";
24/// let path = goud_engine::assets::AssetPath::from_string("test.txt".to_string());
25/// let mut context = LoadContext::new(path);
26///
27/// let result = loader.load(bytes, &(), &mut context);
28/// assert!(result.is_ok());
29/// let asset = result.unwrap();
30/// assert_eq!(asset.content, "Hello, World!");
31/// ```
32pub trait AssetLoader: Send + Sync + Clone + 'static {
33 /// The type of asset this loader produces.
34 type Asset: Asset;
35
36 /// The settings type for this loader.
37 ///
38 /// Use `()` if no settings are needed.
39 type Settings: Send + Sync + Clone + Default + 'static;
40
41 /// Returns the file extensions supported by this loader.
42 ///
43 /// Extensions should not include the leading dot (e.g., "png", not ".png").
44 fn extensions(&self) -> &[&str];
45
46 /// Loads an asset from raw bytes.
47 ///
48 /// # Arguments
49 ///
50 /// - `bytes`: The raw bytes of the asset file
51 /// - `settings`: Loader-specific settings
52 /// - `context`: Loading context with asset path and dependency loading
53 ///
54 /// # Returns
55 ///
56 /// The loaded asset, or an error if loading failed.
57 fn load<'a>(
58 &'a self,
59 bytes: &'a [u8],
60 settings: &'a Self::Settings,
61 context: &'a mut LoadContext,
62 ) -> Result<Self::Asset, AssetLoadError>;
63
64 /// Returns whether this loader can load assets with the given extension.
65 ///
66 /// Default implementation checks against `extensions()`.
67 fn supports_extension(&self, extension: &str) -> bool {
68 self.extensions()
69 .iter()
70 .any(|&ext| ext.eq_ignore_ascii_case(extension))
71 }
72}
73
74/// Type-erased asset loader for dynamic dispatch.
75///
76/// This trait allows storing different loader types in a collection.
77pub trait ErasedAssetLoader: Send + Sync + 'static {
78 /// Returns the file extensions supported by this loader.
79 fn extensions(&self) -> &[&str];
80
81 /// Loads an asset from raw bytes, returning a boxed Any.
82 ///
83 /// The caller is responsible for downcasting to the correct asset type.
84 fn load_erased<'a>(
85 &'a self,
86 bytes: &'a [u8],
87 context: &'a mut LoadContext,
88 ) -> Result<Box<dyn std::any::Any + Send>, AssetLoadError>;
89
90 /// Returns whether this loader can load assets with the given extension.
91 fn supports_extension(&self, extension: &str) -> bool;
92
93 /// Returns a boxed clone of this loader for use across thread boundaries.
94 fn clone_boxed(&self) -> Box<dyn ErasedAssetLoader>;
95}
96
97/// Wrapper that implements ErasedAssetLoader for any AssetLoader.
98#[derive(Clone)]
99pub struct TypedAssetLoader<L: AssetLoader> {
100 loader: L,
101 settings: L::Settings,
102}
103
104impl<L: AssetLoader> TypedAssetLoader<L> {
105 /// Creates a new typed asset loader with default settings.
106 pub fn new(loader: L) -> Self {
107 Self {
108 loader,
109 settings: L::Settings::default(),
110 }
111 }
112
113 /// Creates a new typed asset loader with custom settings.
114 pub fn with_settings(loader: L, settings: L::Settings) -> Self {
115 Self { loader, settings }
116 }
117
118 /// Returns a reference to the inner loader.
119 pub fn loader(&self) -> &L {
120 &self.loader
121 }
122
123 /// Returns a reference to the settings.
124 pub fn settings(&self) -> &L::Settings {
125 &self.settings
126 }
127
128 /// Returns a mutable reference to the settings.
129 pub fn settings_mut(&mut self) -> &mut L::Settings {
130 &mut self.settings
131 }
132}
133
134impl<L: AssetLoader> ErasedAssetLoader for TypedAssetLoader<L> {
135 fn extensions(&self) -> &[&str] {
136 self.loader.extensions()
137 }
138
139 fn load_erased<'a>(
140 &'a self,
141 bytes: &'a [u8],
142 context: &'a mut LoadContext,
143 ) -> Result<Box<dyn std::any::Any + Send>, AssetLoadError> {
144 let asset = self.loader.load(bytes, &self.settings, context)?;
145 Ok(Box::new(asset))
146 }
147
148 fn supports_extension(&self, extension: &str) -> bool {
149 self.loader.supports_extension(extension)
150 }
151
152 fn clone_boxed(&self) -> Box<dyn ErasedAssetLoader> {
153 Box::new(self.clone())
154 }
155}