use bevy::{asset::AssetPath, prelude::*};
pub use bevy_assets_extensions_macros::{AssetLoaderManager, asset_loader_manager};
#[derive(Default)]
pub struct AssetsMonitor {
pub assets: Vec<UntypedHandle>,
}
impl AssetsMonitor {
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
}
}
pub trait AssetLoaderManager: Resource {
type States: States + bevy::state::state::FreelyMutableState;
fn monitor(
&mut self,
handle: UntypedHandle,
next_state: &mut bevy::prelude::NextState<Self::States>,
);
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;
}
pub trait IntoArgs<T>: Sized {
#[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())
}
}
pub trait AssetsLoader {
type Args<'a>;
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");
}
}