Skip to main content

Crate bevy_elf

Crate bevy_elf 

Source
Expand description

This crate loads and resolves assets that reference other assets by name. It builds on serde and integrates into bevy’s asset ecosystem.

Hand-written asset types use Handles, which aren’t serializable. This crate generates a serializable “Def” counterpart for each type, using plain strings in place of Handles, along with a FromDef impl that converts a Def into its runtime type — resolving each string into a Handle by using LoadContext::load() along the way.

§Basic usage

Let’s say you have an animation asset water_animation.ron, which references its spritesheet by name:

(
    frames: [1, 2, 3,],
    frame_duration: (
        secs: 0,
        nanos: 128000000,
    ),
    spritesheet: "water",
)

The corresponding struct would look something like this:


#[derive(FromDef, Asset, TypePath)]
struct AnimationAsset {
    frames: Vec<usize>,
    frame_duration: Duration,
    spritesheet: Handle<Spritesheet>,
}

The FromDef derive macro generates a (de)serializable version of the struct as well as an implementation of the FromDef trait, which converts it into your struct. The generated struct looks something like this:

#[derive(Serialize, Deserialize)]
struct AnimationDef {
    frames: Vec<usize>,
    frame_duration: Duration,
    spritesheet: String,
}

Assets, that implement FromDef can be loaded with the RonAssetLoader, which calls FromDef::from_def() to convert the raw deserialized structure into the runtime structure. You can register the asset and the RonAssetLoader manually or use the AppExt extension trait:

app.init_ron_asset::<AnimationAsset>().init_ron_asset::<Spritesheet>();

To resolve the string names into handles some metadata needs to be provided. Let’s take the Spritesheet asset as an example:

#[derive(FromDef, Asset, TypePath)]
#[asset_spec(base_path = "spritesheets", extension = "ron")]
struct Spritesheet { /* fields omitted */ }

With the asset_spec provided the spritesheet handles inside the AnimationAsset can now be resolved, e.g. the “water” spritesheet gets resolved into “spritesheets/water.ron”.

§Resolving foreign types with elf

Asset types you don’t own cannot be annotated with attributes. Let’s take a closer look at the Spritesheet asset:

#[derive(FromDef, Asset, TypePath)]
#[asset_spec(base_path = "spritesheets", extension = "ron")]
struct Spritesheet {
    #[elf(with_spec(base_path = "spritesheets/images", extension = "png"))]
    image: Handle<Image>,

    #[elf(with_spec(base_path = "spritesheets/layouts", extension = "ron"))]
    layout: Handle<TextureAtlasLayout>,
}

Since Image and TextureAtlasLayout are not defined by you they cannot be resolved the same way, because they don’t have an asset_spec. For these you can use the elf field attribute to tell bevy_elf how to resolve them as shown above.

§Implicit fields

It is also possible to make asset references implicit by their name. Imagine your assets are organized in the file system like this:

assets/
├── animations/
│   ├── water.ron
│   └── grass.ron
└── spritesheets/
    ├── water.ron
    ├── grass.ron
    ├── images/
    │   ├── water.png
    │   └── grass.png
    └── layouts/
        ├── water.ron
        └── grass.ron

Explicitly mentioning e.g. “water” everywhere is cumbersome. Make it implicit instead. The implicit flag goes along well with sub_path:

#[derive(FromDef, Asset, TypePath)]
#[asset_spec(base_path = "spritesheets", extension = "ron")]
struct Spritesheet {
    #[elf(implicit, with_spec(sub_path = "images", extension = "png"))]
    image: Handle<Image>,

    #[elf(implicit, with_spec(sub_path = "layouts", extension = "ron"))]
    layout: Handle<TextureAtlasLayout>,
}

That way the image and layout fields are omitted in the generated def type and don’t show up in the ron file at all. They are resolved with the same string name as their parent.

§Omitting empty def files

With the fields being implicit the spritesheet ron files are now empty. Having to put an empty file there for it all to work isn’t nice at all! To omit the whole file tell bevy_elf to omit the def type entirely and use () instead. Just tell the referencing AnimationAsset to not load any file but put a default value into FromDef::from_def() instead:

#[derive(FromDef, Asset, TypePath)]
struct AnimationAsset {
    frames: Vec<usize>,
    frame_duration: Duration,

    #[elf(from_default)]
    spritesheet: Handle<Spritesheet>,
}

#[derive(FromDef)]
#[elf(def_type(()))]
struct Spritesheet {
    #[elf(implicit, with_spec(base_path = "images", extension = "png"))]
    image: Handle<Image>,

    #[elf(implicit, with_spec(base_path = "layouts", extension = "ron"))]
    layout: Handle<TextureAtlasLayout>,
}

That way your directory structure, as well as the ron files themselves become leaner and the spritesheet doesn’t appear in the file system at all anymore. But the image and its layout are still neatly stored in their own Spritesheet struct.

assets/
├── animations/
│   ├── water.ron
│   └── grass.ron
├── images/
│   ├── water.png
│   └── grass.png
└── layouts/
    ├── water.ron
    └── grass.ron

Note that since the spritesheets directory doesn’t exist anymore, images/ and layouts/ move up to become top-level asset folders, so we changed from sub_path back to base_path. Also note, that Spritesheet is no Asset anymore, since it doesn’t get loaded from a file, so the registration via app.init_ron_asset::<Spritesheet>(); disappears as well.

For more attributes and options see FromDef.

Structs§

AssetRef
A Handle with the assets string id preserved.
DynamicPathResolver
PathResolver
An asset resolver, that assumes the given string id is the complete asset path and returns it as an AssetPath unchanged.
ResolverSpec
An adapter type, that implements AssetResolver by using an AssetPathSpec (S).
RonAssetLoader
Loads assets, which implement FromDef from ron files passing the deserialized FromDef::Def value into the assets FromDef::from_def method.
SpecResolver
StaticResolverAdapter
An adapter type, that implements AssetResolver by delegating to a StaticAssetResolver (S)

Enums§

FromDefError
ResolveError
RonAssetLoadError

Traits§

AppExt
Extension trait for App to register a ron asset with a FromDef implementation and a corresponding RonAssetLoader by calling app.init_ron_asset::<MyRonAsset>().
AssetPathSpec
AssetPathSpecProvider
An AssetResolver using a base_path and an optional file extension.
AssetResolver
Resolves the AssetPath for a given string id.
FromDef
Represents a type, that can be constructed from a deserializable def type. An asset type implementing FromDef can be loaded by the RonAssetLoader. It deserializes the asset bytes into the FromDef::Def type and then turns it into the runtime asset type which implements FromDef by passing it to FromDef::from_def(). This trait can be implemented manually or by using the FromDef derive macro. To enable loading ron assets implementing FromDef init the asset and loader via AppExt::init_ron_asset().
FromDefWithResolver
Like FromDef, but uses an explicitly passed AssetResolver to resolve its AssetPath.
HasResolver
HasSpecProvider
StaticAssetResolver
Resolves the AssetPath for a given string asset id from a static context, meaning to self object is nessecary.

Functions§

extract_id_from
Extracts the string id of the asset from its AssetPath.

Attribute Macros§

asset_spec
Generates an implementation of AssetPathSpec and HasResolver for the annotated struct or enum. This enables this asset to be resolved from a file name prefix when used as a field of a type implementing FromDef.

Derive Macros§

FromDef
Implements the trait FromDef for the annotated struct or enum, by converting all contained fields via their FromDef::from_def() implementation. So all fields have to implement FromDef. All primitive types, container types like Option, Vec and HashMap, as well as Handle and AssetRef implement FromDef.