mini_asset_loader/lib.rs
1//! The mini asset loader provides a simple set of utilities to enable the loading of assets
2//! by their name for use in, e.g., a game.
3//!
4//! ## Loaders
5//!
6//! - The [loaders::CachedLoader] provides the ability to cache assets in-memory between loads,
7//! with full user control over when unused assets are unloaded. The [loaders::ToCached] trait,
8//! which is implemented for all loaders, allows you to use `to_cached()` to convert a loader into
9//! a cached one.
10//! - A simple `Vec<Box<dyn AssetLoader>>` can search multiple loaders and load the first successful
11//! result, which is made easier via the provided `asset_loader_vec!` macro.
12//! - A [PathBuf] acts as a loader to load assets from a specific path on disk.
13//! - The [loaders::ZipLoader] can load assets from a ZIP file.
14//! - A simple [HashMap] can be used as a loader for assets stored in memory.
15//! These loaders can be composed in various ways to create more advanced behaviour.
16//!
17//! ## Asset Creation Handlers
18//!
19//! An [AssetCreationHandler] implementation is required - this provides the function that actually
20//! *creates* an asset, meaning this is the ideal place to implement custom deserialization, allocation,
21//! etc. for your custom asset type.
22//!
23//! For a simple implementation of this, which provides a helpful example of the asset system in use,
24//! (but which, unfortunately, requires nightly rust), see the [asset] module.
25//!
26//! The [ExtensionMappedAssetCreationHandler] is a builtin type that allows one to map different file
27//! extensions on the asset identifier string to different AssetCreationHandlers. For example, one could use
28//! the builtin `asset` module's creation handler for `.json`-based types, and use a separate mesh creation
29//! handler for `.dae` types.
30//!
31//! ## Features
32//!
33//! - `zip` - Provides the [loaders::zip] module, containing a ZIP file loader.
34//! - `asset` - Provides the [asset] module, containing a simple JSON asset implementation.
35
36#![cfg_attr(all(feature = "asset", nightly), feature(trait_upcasting))]
37#[cfg(feature = "asset")]
38pub mod asset;
39
40pub mod loaders;
41
42pub use any_handle::AnyHandle;
43use std::any::Any;
44use std::collections::HashMap;
45use std::fs::File;
46use std::io::{BufReader, Read};
47use std::path::{Path, PathBuf};
48
49/// An AssetCreationHandler is a delegate that handles the creation (usually deserialization)
50/// and allocation of assets from an input byte stream.
51///
52/// You could compose multiple AssetCreationHandlers by creating a handler that maps to other
53/// handlers based on the identifier's file extension or magic numbers in the input stream.
54/// See [ExtensionMappedAssetCreationHandler].
55pub trait AssetCreationHandler {
56 fn create_asset(&mut self, identifier: &str, reader: &mut dyn Read) -> Option<Box<dyn Any>>;
57}
58
59/// Maps to multiple [AssetCreationHandler]s based on the file extension of the asset.
60///
61/// ## Example
62///
63/// ```
64/// # use std::any::Any;
65/// # use std::io::Read;
66/// # use mini_asset_loader::{AssetCreationHandler, ExtensionMappedAssetCreationHandler};
67/// # struct MyJsonHandler {}
68/// # impl AssetCreationHandler for MyJsonHandler {
69/// # fn create_asset(&mut self, identifier: &str, reader: &mut dyn Read) -> Option<Box<dyn Any>> {
70/// # None
71/// # }
72/// # }
73/// # struct MyMeshHandler {}
74/// # impl AssetCreationHandler for MyMeshHandler {
75/// # fn create_asset(&mut self, identifier: &str, reader: &mut dyn Read) -> Option<Box<dyn Any>> {
76/// # None
77/// # }
78/// # }
79/// let mut handler = ExtensionMappedAssetCreationHandler::new()
80/// .with("json", MyJsonHandler {}) // Use MyJsonHandler on .json files
81/// .with("dae", MyMeshHandler {}); // Use MyMeshHandler on .dae files
82/// ```
83#[derive(Default)]
84pub struct ExtensionMappedAssetCreationHandler {
85 handlers: HashMap<String, Box<dyn AssetCreationHandler>>,
86}
87
88impl ExtensionMappedAssetCreationHandler {
89 /// Creates a default ExtensionMappedAssetCreationHandler.
90 /// Use [with] to add extensions.
91 pub fn new() -> Self {
92 ExtensionMappedAssetCreationHandler {
93 handlers: HashMap::default(),
94 }
95 }
96 /// Returns a version of this Handler with an additional child Handler.
97 pub fn with<T: AssetCreationHandler + 'static>(mut self, key: &str, handler: T) -> Self {
98 self.handlers.insert(key.to_string(), Box::new(handler));
99 self
100 }
101}
102
103impl AssetCreationHandler for ExtensionMappedAssetCreationHandler {
104 /// Handles extension-mapped asset creation.
105 fn create_asset(&mut self, identifier: &str, reader: &mut dyn Read) -> Option<Box<dyn Any>> {
106 let ext = Path::new(identifier).extension()?.to_str()?;
107 let handler = self.handlers.get_mut(ext)?;
108 handler.create_asset(identifier, reader)
109 }
110}
111
112/// An AssetLoader loads an asset given its name, with help from an [AssetCreationHandler].
113/// Some AssetLoaders implement special behaviour, such as caching or combining multiple
114/// child loaders.
115pub trait AssetLoader {
116 /// Load an asset with the given identifier.
117 ///
118 /// Returns None if the asset could not be found or failed to serialize.
119 fn load_asset(
120 &self,
121 handler: &mut dyn AssetCreationHandler,
122 identifier: &str,
123 ) -> Option<AnyHandle<dyn Any>>;
124}
125
126/// A simple HashMap can act as a loader for a set of values in memory.
127impl AssetLoader for HashMap<String, AnyHandle<dyn Any>> {
128 fn load_asset(
129 &self,
130 _handler: &mut dyn AssetCreationHandler,
131 identifier: &str,
132 ) -> Option<AnyHandle<dyn Any>> {
133 self.get(identifier).cloned()
134 }
135}
136
137/// A Vec<Box<dyn AssetLoader>> or similar structure can act as a combined loader over its elements,
138/// querying them one by one. The `boxed_vec!` macro can help with this. Prefer this over the use of
139/// a CombinedLoader.
140impl AssetLoader for Vec<Box<dyn AssetLoader>> {
141 fn load_asset(
142 &self,
143 handler: &mut dyn AssetCreationHandler,
144 identifier: &str,
145 ) -> Option<AnyHandle<dyn Any>> {
146 self.iter().find_map(|x| x.load_asset(handler, identifier))
147 }
148}
149
150/// A PathBuf can act as a loader for files relative to the directory it points to.
151impl AssetLoader for PathBuf {
152 fn load_asset(
153 &self,
154 handler: &mut dyn AssetCreationHandler,
155 identifier: &str,
156 ) -> Option<AnyHandle<dyn Any>> {
157 let mut new_path: PathBuf = self.to_path_buf();
158 new_path.push(identifier);
159
160 if !new_path.is_file() {
161 return None;
162 }
163
164 let res =
165 handler.create_asset(identifier, &mut BufReader::new(File::open(new_path).ok()?))?;
166
167 Some(AnyHandle::<dyn Any>::new(res))
168 }
169}
170
171/// Allows for easy creation of a vector of boxed asset loaders.
172/// Use it the same as you would use `vec!`. Each element will be passed through `Box::new`.
173#[macro_export]
174macro_rules! asset_loader_vec {
175 () => {
176 Vec::<Box<dyn AssetLoader>>::new()
177 };
178 ($($x:expr),+ $(,)?) => {
179 vec![$(Box::new($x) as Box<dyn AssetLoader>),+]
180 };
181}
182
183/// A TypedAssetLoader is a simple helper for AssetLoaders that can downcast
184/// them into a handle matching their type.
185pub trait TypedAssetLoader {
186 /// Load an asset by its identifier, trying to interpret it as the given type.
187 ///
188 /// Returns None if the asset could not be found, failed to serialize, or was not of the
189 /// correct type.
190 fn load_typed_asset<T: Any>(
191 &self,
192 handler: &mut dyn AssetCreationHandler,
193 identifier: &str,
194 ) -> Option<AnyHandle<T>>;
195}
196
197/// Catch-all TypedAssetLoader implementation for any AssetLoader.
198impl<T> TypedAssetLoader for T
199where
200 T: AssetLoader,
201{
202 fn load_typed_asset<Y: Any>(
203 &self,
204 handler: &mut dyn AssetCreationHandler,
205 identifier: &str,
206 ) -> Option<AnyHandle<Y>> {
207 let result = self.load_asset(handler, identifier)?;
208 result.into()
209 }
210}