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>()
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.
The actual call to load
hasn't changed:
let handle = server.load("path/to/my/asset.json");
// ...
let data = assets.get(&handle).unwrap();
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.