bevy_assets_extensions 0.7.0

Extensions for bevy assets, with support of collection as assets and a loader manager.
Documentation
//! Module with support for assets loading.
use bevy::{asset::AssetPath, prelude::*};

pub use bevy_assets_extensions_macros::{AssetLoaderManager, asset_loader_manager};

/// Structure used by the asset loader mechanism to monitor assets loading.
/// It is needed as a field for the asset loader. It is added to the structure
/// by the macro `asset_loader_manager`.
#[derive(Default)]
pub struct AssetsMonitor {
    /// List of assets been monitored
    pub assets: Vec<UntypedHandle>,
}

impl AssetsMonitor {
    /// Monitor the untyped handle
    pub fn monitor_asset(&mut self, handle: UntypedHandle) {
        self.assets.push(handle);
    }
    fn load_asset<'a, T: Asset>(
        &mut self,
        t: AssetPath<'a>,
        asset_server: &AssetServer,
    ) -> Handle<T> {
        let handle = asset_server.load(t);
        self.monitor_asset(handle.clone().untyped());
        handle
    }
}

/// Base trait for assets loader
pub trait AssetLoaderManager: Resource {
    /// Type of the states
    type States: States + bevy::state::state::FreelyMutableState;
    /// Monitor a handle
    fn monitor(
        &mut self,
        handle: UntypedHandle,
        next_state: &mut bevy::prelude::NextState<Self::States>,
    );
    /// Load a bundle of assets
    fn load<'a, T: AssetsLoader>(
        &mut self,
        t: impl IntoArgs<T::Args<'a>>,
        asset_server: &AssetServer,
        next_state: &mut bevy::prelude::NextState<Self::States>,
    ) -> T;
}

/// Trait for converting arguments to load function to the right type.
pub trait IntoArgs<T>: Sized {
    /// Converts this type into the (usually inferred) input type.
    #[must_use]
    fn into(self) -> T;
}

impl<'a, T> IntoArgs<AssetPath<'a>> for T
where
    T: Into<AssetPath<'a>>,
{
    fn into(self) -> AssetPath<'a> {
        Into::into(self)
    }
}

impl<'a, T0, T1> IntoArgs<(AssetPath<'a>, AssetPath<'a>)> for (T0, T1)
where
    T0: Into<AssetPath<'a>>,
    T1: Into<AssetPath<'a>>,
{
    fn into(self) -> (AssetPath<'a>, AssetPath<'a>) {
        (self.0.into(), self.1.into())
    }
}

/// Base trait for the asset loader.
pub trait AssetsLoader {
    /// Type of the arguments.
    type Args<'a>;
    /// Load the arguments with the given asset server and monitor.
    fn load<'a>(
        t: Self::Args<'a>,
        asset_server: &AssetServer,
        assets_monitor: &mut AssetsMonitor,
    ) -> Self;
}

impl<T0: Asset> AssetsLoader for Handle<T0> {
    type Args<'a> = AssetPath<'a>;
    fn load<'a>(
        t: Self::Args<'a>,
        asset_server: &AssetServer,
        assets_monitor: &mut AssetsMonitor,
    ) -> Self {
        assets_monitor.load_asset::<T0>(t, asset_server)
    }
}

impl<T0: Asset, T1: Asset> AssetsLoader for (Handle<T0>, Handle<T1>) {
    type Args<'a> = (AssetPath<'a>, AssetPath<'a>);
    fn load<'a>(
        t: Self::Args<'a>,
        asset_server: &AssetServer,
        assets_monitor: &mut AssetsMonitor,
    ) -> Self {
        (
            assets_monitor.load_asset::<T0>(t.0, asset_server),
            assets_monitor.load_asset::<T1>(t.1, asset_server),
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{assets::text, prelude::*};
    use bevy::state::app::StatesPlugin;
    use std::time::Duration;
    #[asset_loader_manager(State: from Loading to Completed)]
    #[derive(Resource)]
    struct TestAssetLoaderManager {}

    #[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
    enum State {
        #[default]
        Loading,
        Completed,
    }

    #[derive(Resource, Default)]
    struct Handles {
        hello: Handle<text::Text>,
        world: Handle<text::Text>,
    }

    #[test]
    fn test_asset_loader_manager() {
        let mut app = App::new();
        app.add_plugins((MinimalPlugins, AssetPlugin::default(), StatesPlugin));
        app.init_asset::<assets::Text>();
        app.init_asset_loader::<assets::text::TextLoader>();
        app.init_state::<State>();
        app.init_resource::<Handles>();
        use crate::assets_loader::AssetLoaderManager as ALM;
        app.init_resource::<TestAssetLoaderManager>();
        app.add_systems(
            Startup,
            |asset_server: Res<AssetServer>,
             mut loader: ResMut<TestAssetLoaderManager>,
             mut handles: ResMut<Handles>,
             mut next_state: ResMut<NextState<State>>| {
                (handles.hello, handles.world) = loader.load(
                    ("test_relative/hello.txt", "world.txt"),
                    asset_server.as_ref(),
                    &mut next_state,
                );
            },
        );

        while *app.world().resource::<bevy::prelude::State<State>>().get() != State::Completed {
            app.update();
            std::thread::sleep(Duration::from_secs(1));
        }

        let handles = app.world().resource::<Handles>();

        let hello = app
            .world()
            .resource::<Assets<assets::Text>>()
            .get(&handles.hello)
            .unwrap();
        assert_eq!(hello.text_ref(), "hello");
        let world = app
            .world()
            .resource::<Assets<assets::Text>>()
            .get(&handles.world)
            .unwrap();
        assert_eq!(world.text_ref(), "world");
    }
}