1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
//! Conveniently load, store and cache external resources.
//!
//! This crate aims at providing a filesystem abstraction to easily load external resources.
//! It was originally thought for games, but can, of course, be used in other contexts.
//!
//! The structure [`AssetCache`] is the entry point of the crate.
//!
//! # Cargo features
//!
//! - `hot-reloading`: Add hot-reloading.
//! - `macros`: Add support for deriving `Asset` trait.
//!
//! ### Additional sources
//!
//! These features enable to read assets from other sources than the file
//! system. They are defined in the [`source`] module.
//!
//! - `embedded`: Embed assets files directly in your binary.
//! - `zip`: Read asset from zip archives. Note that no decompression method is
//! enabled by default, but you can do so with the following features:
//! - `zip-bzip2`: Enable `bzip2` decompression.
//! - `zip-deflate`: Enable `flate2` decompression.
//! - `tar`: Read assets from TAR archives.
//!
//! ### Additional formats
//!
//! These features add support for asset formats. There is one feature per
//! format.
//!
//! - Serialisation formats (with [`serde`] crate): `bincode`, `json`,
//! `msgpack`, `ron`, `toml`, `yaml`.
//! - Audio formats (with [`rodio`] crate up to (`0.17`)): `mp3`, `flac`, `vorbis`, `wav`.
//! - Image formats (with [`image`] crate): `bmp`, `jpeg`, `png` `webp`.
//! - 3D formats (with [`gltf`] crate): `gltf`.
//!
//! ## External crates support
//!
//! Support of some other crates is done in external crates:
//! - [`ggez`](https://github.com/ggez/ggez): [`ggez-assets_manager`](https://crates.io/crates/ggez-assets_manager)
//! - [`kira`](https://github.com/tesselode/kira): [`assets_manager-kira`](https://crates.io/crates/assets_manager-kira)
//! - [`rodio`](https://github.com/RustAudio/rodio) (starting from `0.18`): [`assets_manager-rodio`](https://crates.io/crates/assets_manager-rodio)
//!
//! ### Internal features
//!
//! These features change inner data structures implementations.
//!
//! - [`parking_lot`]: Use `parking_lot`'s synchronization primitives.
//! - [`ahash`]: Use a faster hashing algorithm (enabled by default).
//!
//! # Basic example
//!
//! If the file `assets/common/position.ron` contains this:
//!
//! ```text
//! Point(
//! x: 5,
//! y: -6,
//! )
//! ```
//!
//! Then you can load it this way (with feature `ron` enabled):
//!
//! ```
//! # cfg_if::cfg_if! { if #[cfg(feature = "ron")] {
//! use assets_manager::{Asset, AssetCache, loader};
//! use serde::Deserialize;
//!
//! // The struct you want to load
//! #[derive(Deserialize)]
//! struct Point {
//! x: i32,
//! y: i32,
//! }
//!
//! // Specify how you want the structure to be loaded
//! impl Asset for Point {
//! // The extension of the files to look into
//! const EXTENSION: &'static str = "ron";
//!
//! // The serialization format
//! type Loader = loader::RonLoader;
//! }
//!
//!
//! // Create a new cache to load assets under the "./assets" folder
//! let cache = AssetCache::new("assets")?;
//!
//! // Get a handle on the asset
//! // This will load the file `./assets/common/position.ron`
//! let handle = cache.load::<Point>("common.position")?;
//!
//! // Lock the asset for reading
//! // Any number of read locks can exist at the same time,
//! // but none can exist when the asset is reloaded
//! let point = handle.read();
//!
//! // The asset is now ready to be used
//! assert_eq!(point.x, 5);
//! assert_eq!(point.y, -6);
//!
//! // Loading the same asset retreives it from the cache
//! let other_handle = cache.load("common.position")?;
//! assert!(std::ptr::eq(handle, other_handle));
//! # }}
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # Hot-reloading
//!
//! Hot-reloading is a major feature of `assets_manager`: when a file is added,
//! modified or deleted, the values of all loaded assets that depend on this
//! file are automatically and transparently updated.
//!
//! To use hot-reloading, see [`AssetCache::hot_reload`].
//!
//! See the [`asset`] module for a precise description of how assets interact
//! with hot-reloading.
//!
//! # Ownership model
//!
//! You will notice that you cannot get owned [`Handle`]s, only references whose
//! lifetime are tied to that of the [`AssetCache`] from which there was loaded.
//! This may be seen as a weakness, as `'static` data is generally easier to
//! work with, but it is actually a clever use of Rust ownership system.
//!
//! As when you borrow an `&str` from a `String`, an `&Handle` guarantees
//! that the underlying asset is stored in the cache. This is especially useful
//! with hot-reloading: all `&Handle` are guaranteed to be reloaded when
//! possible, so two handles on the same asset always have the same value. This
//! would not be possible if `Handle`s were always `'static`.
//!
//! Note that this also means that you need a mutable reference to a cache to
//! remove assets from it.
//!
//! ## Getting owned data
//!
//! Working with owned data is far easier: you don't have to care about
//! lifetimes, it can easily be sent to other threads, etc. This section gives
//! a few techniques to work with the fact that caches give references to their
//! assets.
//!
//! Note that none of these proposals is compulsory to use this crate: you can
//! work with non-`'static` data, or invent your own techniques.
//!
//! ### Getting a `&'static AssetCache`
//!
//! Because the lifetime of a `Handle` reference is tied to that of the `&AssetCache`,
//! this makes possible to get `&'static Handle`s. Moreover, it enables you to
//! call [`AssetCache::enhance_hot_reloading`], which is easier to work with
//! than the default solution.
//!
//! You get easily get a `&'static AssetCache`, with the `once_cell` crate or
//! [`std::sync::OnceLock`], but you can also do it by [leaking a `Box`](Box::leak).
//!
//! Note that using this technique prevents you from removing assets from the
//! cache, so you have to keep them in memory for the duration of the program.
//! This also creates global state, which you might want to avoid.
//!
//! ### Cloning assets
//!
//! Assets being `'static` themselves, cloning them is a good way to opt out of
//! the lifetime of the cache. If cloning the asset itself is too expensive, you
//! can take advantage of the fact that `Arc<T>` is an asset if `T` is too and
//! that cloning an `Arc` is a rather cheap operation.
//!
//! Not that this usually does not work wery well with hot-reloading.
//!
//! ### Storing `String`s
//!
//! Strings are `'static` and easy to work with, and you can use them to load
//! an asset from the cache, which is a cheap operation if the asset is already
//! stored in it. If you want to ensure that no heavy operation is used, you
//! can do so with [`AssetCache::get_cached`].
//!
//! If you have to clone them a lot, you may consider changing your `String`
//! into an `Arc<str>` or a `SharedString` which is cheaper to clone.
//!
//! This is the technique internally used by `assets_manager` to store
//! directories.
#![warn(missing_docs, missing_debug_implementations)]
#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate self as assets_manager;
mod anycache;
pub use anycache::{AnyCache, AsAnyCache};
pub mod asset;
pub use asset::{Asset, Compound, Storable};
mod cache;
pub use cache::AssetCache;
mod dirs;
pub use dirs::{Directory, RecursiveDirectory};
mod error;
pub use error::{BoxedError, Error};
pub mod loader;
mod local_cache;
pub use local_cache::LocalAssetCache;
mod entry;
pub use entry::{AssetReadGuard, AtomicReloadId, Handle, ReloadId, ReloadWatcher, UntypedHandle};
mod key;
pub mod source;
#[cfg_attr(not(feature = "hot-reloading"), path = "hot_reloading/disabled.rs")]
pub mod hot_reloading;
mod utils;
#[cfg(feature = "utils")]
#[cfg_attr(docsrs, doc(cfg(feature = "utils")))]
pub use utils::cell::OnceInitCell;
pub use utils::{SharedBytes, SharedString};
/// Implements [`Asset`] for a type.
///
/// Note that the type must implement the right traits for it to work (eg
/// `serde::Deserialize` or `std::str::FromStr`).
///
/// # Supported formats
///
/// - `"json"`: Use [`loader::JsonLoader`] and extension `.json`
/// - `"ron"`: Use [`loader::RonLoader`] and extension `.ron`
/// - `"toml"`: Use [`loader::TomlLoader`] and extension `.toml`
/// - `"txt"`: Use [`loader::ParseLoader`] and extension `.txt`
/// - `"yaml"` or `"yml"`: Use [`loader::YamlLoader`] and extensions `.yaml` and `.yml`
///
/// # Example
///
/// ```rust
/// # cfg_if::cfg_if! { if #[cfg(feature = "ron")] {
/// # use assets_manager::{Asset, AssetCache, BoxedError};
/// // Define a type loaded as ron
/// #[derive(Asset, serde::Deserialize)]
/// #[asset_format = "ron"]
/// struct Point {
/// x: i32,
/// y: i32,
/// }
///
/// // Define a type loaded as text
/// #[derive(Asset)]
/// #[asset_format = "txt"]
/// struct Name(String);
///
/// impl std::str::FromStr for Name {
/// type Err = BoxedError;
///
/// fn from_str(s: &str) -> Result<Self, BoxedError> {
/// Ok(Self(String::from(s)))
/// }
/// }
///
/// let cache = AssetCache::new("assets")?;
///
/// // Load "assets/common/position.ron"
/// let position = cache.load::<Point>("common.position")?;
/// assert_eq!(position.read().x, 5);
/// assert_eq!(position.read().y, -6);
///
/// // Load "assets/common/name.txt"
/// let name = cache.load::<Name>("common.name")?;
/// assert_eq!(name.read().0, "Aragorn");
/// # }}
/// # Ok::<(), assets_manager::BoxedError>(())
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
#[cfg(feature = "macros")]
pub use assets_manager_macros::Asset;
#[cfg(test)]
mod tests;