NiklasEi / bevy_asset_loader

Bevy plugin helping with asset loading and organization
Apache License 2.0
474 stars 52 forks source link

bevy_asset_loader doesn't wait for asset dependencies #70

Closed InnocentusLime closed 2 years ago

InnocentusLime commented 2 years ago

In this minimal example the app will crash, since neither bevy's AssetServer or bevy_asset_loader wait for asset dependencies. So AssetA gets loaded successfully, but its slow dependency may not be loaded when you enter InGame state.

use bevy::prelude::*;
use bevy_asset_loader::*;
use iyes_loopless::prelude::*;

use anyhow::Error;
use bevy::reflect::TypeUuid;
use bevy::asset::{ 
    AssetLoader as AssetLoaderTrait, LoadContext, BoxedFuture, 
    LoadedAsset, Assets, AssetPath
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum GameState {
    Boot,
    InGame,
}

// Imagine Asset A is some lightweight fast-to-load asset (like a config)
#[derive(TypeUuid)]
#[uuid="cc570c16-ffd0-11ec-b939-0242ac120003"]
struct AssetA;

// And Asset B is some heavy slow-to-load asset (a quite big texture) 
#[derive(TypeUuid)]
#[uuid="cc570c16-ffd0-11ec-b939-0242ac120002"]
struct AssetB;

#[derive(Clone, Copy, Default)]
struct AssetALoader;

impl AssetLoaderTrait for AssetALoader {
    fn load<'a>(
        &'a self, _bytes: &'a [u8], load_context: &'a mut LoadContext
    ) -> BoxedFuture<Result<(), Error>> { 
        Box::pin(async move {
            let mut asset = LoadedAsset::new(AssetA);
            asset.add_dependency("B.b".into());
            load_context.set_default_asset(asset);
            Ok(())
        })
    }

    fn extensions(&self) -> &[&str] { &["a"] }
}

#[derive(Clone, Copy, Default)]
struct AssetBLoader;

impl AssetLoaderTrait for AssetBLoader {
    fn load<'a>(
        &'a self, _bytes: &'a [u8], load_context: &'a mut LoadContext
    ) -> BoxedFuture<Result<(), Error>> { 
        Box::pin(async move {
            use std::time::Duration;

            let asset = LoadedAsset::new(AssetB);
            // Pretend that we are parsing a large file (a .png)
            std::thread::sleep(Duration::from_secs(10));
            load_context.set_default_asset(asset);
            Ok(())
        })
    }

    fn extensions(&self) -> &[&str] { &["b"] }
}

#[derive(AssetCollection)]
struct ACol {
    #[asset(path = "A.a")]
    _asset: Handle<AssetA>,
}

fn test_enter(assets_a: Res<Assets<AssetA>>, assets_b: Res<Assets<AssetB>>) {
    assert!(assets_a.get::<AssetPath>("A.a".into()).is_some());
    println!("asset A loaded");
    assert!(assets_b.get::<AssetPath>("B.b".into()).is_some());
    println!("asset B loaded");
}

fn main() {
    let mut app = App::new();

    app
        .add_plugins(DefaultPlugins)
        .add_asset::<AssetA>()
        .add_asset::<AssetB>()
        .init_asset_loader::<AssetALoader>()
        .init_asset_loader::<AssetBLoader>()
        .add_loopless_state(GameState::Boot)
        .add_enter_system(GameState::InGame, test_enter);

    AssetLoader::new(GameState::Boot)
        .continue_to_state(GameState::InGame)
        .with_collection::<ACol>()
        .build(&mut app);

    app.run();
}
NiklasEi commented 2 years ago

This seems to be a duplicate of #63

It also seems like weird behaviour from Bevy itself.

InnocentusLime commented 2 years ago

I don't know if it is weird or not. Looking through AssetServer code, it is revealed, that bevy handles dependencies asynchronously, but never .awaits them.

InnocentusLime commented 2 years ago

This seems to be a duplicate of #63

It also seems like weird behaviour from Bevy itself.

Indeed it is! Closing as a duplicate