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}