Skip to main content

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}