Skip to content

Instantly share code, notes, and snippets.

@viridia
Last active September 13, 2023 21:28
Show Gist options
  • Save viridia/78877d192e666c94c496b6b29c89de27 to your computer and use it in GitHub Desktop.
Save viridia/78877d192e666c94c496b6b29c89de27 to your computer and use it in GitHub Desktop.
Migrating to Bevy Assets V2

Migrating to Bevy Assets V2

Migrating a custom asset loader

Existing asset loaders will need a few small changes to get them to work with Bevy Assets V2.

First, you'll need to add the asset type as an associated type of the loader. This type is called Asset and represents the type of the "default asset" produced by the loader.

You'll also need to add a Settings type which represents options that can be passed to the loader when you request an asset. If your asset has no settings, then you can just set it to the unit type.

pub struct MyAssetLoader;

impl AssetLoader for MyAssetLoader {
    type Asset = MyAsset;
    type Settings = ();

You'll need to make a couple small changes to the load function as well. The load function now takes a settings parameter whose type is, you guessed it, Settings:

    fn load<'a>(
        &'a self,
        reader: &'a mut Reader,
        settings: &'a Self::Settings,
        load_context: &'a mut LoadContext,
    ) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {

Again, if you are not using settings, then you can just ignore the parameter (prefix it with "_").

Also, the second argument is now a reader rather than vector of bytes. If your existing code expects bytes, you can simply read the entire stream:

    fn load<'a>(
        &'a self,
        reader: &'a mut Reader,
        _settings: &'a Self::Settings,
        load_context: &'a mut LoadContext,
    ) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
        Box::pin(async move {
            let mut bytes = Vec::new();
            reader.read_to_end(&mut bytes).await?;

Finally, you'll need to write the code which returns the default asset. This used to be done via a call to load_context.set_default_asset(), however in V2 you simply return the asset from the load function:

    fn load<'a>(
        &'a self,
        reader: &'a mut Reader,
        _settings: &'a Self::Settings,
        load_context: &'a mut LoadContext,
    ) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
        Box::pin(async move {
            let mut bytes = Vec::new();
            reader.read_to_end(&mut bytes).await?;
        let mut asset: MyAsset =
            serde_json::from_slice(&bytes).expect("unable to decode asset");
        Ok(asset)
    }

To use the new loader, make sure you register both the loader and the asset type:

app.register_asset_loader(MyAssetLoader)
    .init_asset::<MyAsset>()

Labeled assets

If your loader allows labeled assets, there are a couple of different ways to handle them. The simplest is to call load_context.labeled_asset_scope:

// Assume `asset.children` is a HashMap or something.
// Using `drain` here so that we take ownership and don't end up with
// multiple references to the same asset.
asset.children.drain().for_each(|(label, mut item)| {
    load_context.labeled_asset_scope(label, |lc| {
        // Do any additional processing on the item
        // Use 'lc' to load dependencies
        item
    });
});

You can use the provided load context (lc) to load additional assets. These will automatically be registered as dependencies of the labeled asset.

Using assets

The actual call to load hasn't changed:

let handle = server.load("path/to/my/asset.json");

// ...

let data = assets.get(&handle).unwrap();

Asset events

There are a few changes to asset events. The event no longer contains a handle field, instead the event contains a field called id:

for ev in ev_template.read() {
    match ev {
        AssetEvent::Added { id } => {
            println!("Asset added");
        }
        
        AssetEvent::LoadedWithDependencies { id } => {
            println!("Asset loaded");
        }
        
        AssetEvent::Modified { id } => {
            println!("Asset modified");
        }

        AssetEvent::Removed { id } => {
            println!("Asset removed");
        }
    }
}

The id can be used to get access to the asset data, the asset's path or load status. Asset handles also contain an id field which can be used to compare for equality:

AssetEvent::Modified { id } => {
    for cmp in query.iter() {
       if cmp.handle.id() == id {
           println!("Found it!");
       }
    }
}

Also, as you may have noticed, the set of events has changed. The most important of these is LoadedWithDependencies which tells you that the asset and all its dependencies have finished loading into memory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment