Crate assets_manager

source ·
Expand description

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): 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:

§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:

Point(
    x: 5,
    y: -6,
)

Then you can load it this way (with feature ron enabled):

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));

§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 Handles, 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 Handles 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 Handles. 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.

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 Strings

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.

Re-exports§

Modules§

  • Values loadable from a cache.
  • Tools to implement hot-reloading.
  • Generic asset loading definition
  • Bytes sources to load assets from.

Structs§

Traits§

Type Aliases§

Derive Macros§