#![forbid(unsafe_code)]
#![warn(unused_imports, missing_docs)]
pub use bevy_asset_loader_derive::AssetCollection;
use bevy::app::App;
use bevy::asset::{AssetServer, HandleUntyped, LoadState};
use bevy::ecs::prelude::IntoExclusiveSystem;
use bevy::ecs::schedule::{State, StateData};
use bevy::prelude::{FromWorld, SystemSet, World};
use bevy::utils::HashMap;
use std::marker::PhantomData;
pub trait AssetCollection: Send + Sync + 'static {
fn create(world: &mut World) -> Self;
fn load(world: &mut World) -> Vec<HandleUntyped>;
}
struct LoadingAssetHandles<A: AssetCollection> {
handles: Vec<HandleUntyped>,
marker: PhantomData<A>,
}
struct AssetLoaderConfiguration<T> {
configuration: HashMap<T, LoadingConfiguration<T>>,
}
impl<T> Default for AssetLoaderConfiguration<T> {
fn default() -> Self {
AssetLoaderConfiguration {
configuration: HashMap::default(),
}
}
}
struct LoadingConfiguration<T> {
next: Option<T>,
count: usize,
}
#[derive(Default)]
pub struct AssetKeys {
keys: HashMap<String, String>,
}
impl AssetKeys {
pub fn get_path_for_key(&self, key: &str) -> &str {
self.keys
.get(key)
.unwrap_or_else(|| panic!("Failed to get a path for key '{}'", key))
}
pub fn set_asset_key<T: Into<String>>(&mut self, key: T, value: T) {
self.keys.insert(key.into(), value.into());
}
}
fn start_loading<T: StateData, Assets: AssetCollection>(world: &mut World) {
{
let cell = world.cell();
let mut asset_loader_configuration = cell
.get_resource_mut::<AssetLoaderConfiguration<T>>()
.expect("Cannot get AssetLoaderConfiguration");
let state = cell.get_resource::<State<T>>().expect("Cannot get state");
let mut config = asset_loader_configuration
.configuration
.get_mut(state.current())
.unwrap_or_else(|| {
panic!(
"Could not find a loading configuration for state {:?}",
state.current()
)
});
config.count += 1;
}
let handles = LoadingAssetHandles {
handles: Assets::load(world),
marker: PhantomData::<Assets>,
};
world.insert_resource(handles);
}
fn check_loading_state<T: StateData, Assets: AssetCollection>(world: &mut World) {
{
let cell = world.cell();
let loading_asset_handles = cell.get_resource::<LoadingAssetHandles<Assets>>();
if loading_asset_handles.is_none() {
return;
}
let loading_asset_handles = loading_asset_handles.unwrap();
let asset_server = cell
.get_resource::<AssetServer>()
.expect("Cannot get AssetServer resource");
let load_state = asset_server
.get_group_load_state(loading_asset_handles.handles.iter().map(|handle| handle.id));
if load_state != LoadState::Loaded {
return;
}
let mut state = cell
.get_resource_mut::<State<T>>()
.expect("Cannot get State resource");
let mut asset_loader_configuration = cell
.get_resource_mut::<AssetLoaderConfiguration<T>>()
.expect("Cannot get AssetLoaderConfiguration resource");
if let Some(mut config) = asset_loader_configuration
.configuration
.get_mut(state.current())
{
config.count -= 1;
if config.count == 0 {
if let Some(next) = config.next.as_ref() {
state.set(next.clone()).expect("Failed to set next State");
}
}
}
}
let asset_collection = Assets::create(world);
world.insert_resource(asset_collection);
world.remove_resource::<LoadingAssetHandles<Assets>>();
}
fn init_resource<Asset: FromWorld + Send + Sync + 'static>(world: &mut World) {
let asset = Asset::from_world(world);
world.insert_resource(asset);
}
pub struct AssetLoader<T> {
next_state: Option<T>,
loading_state: T,
keys: HashMap<String, String>,
load: SystemSet,
check: SystemSet,
post_process: SystemSet,
collection_count: usize,
}
impl<State> AssetLoader<State>
where
State: StateData,
{
pub fn new(load: State) -> AssetLoader<State> {
Self {
next_state: None,
loading_state: load.clone(),
keys: HashMap::default(),
load: SystemSet::on_enter(load.clone()),
check: SystemSet::on_update(load.clone()),
post_process: SystemSet::on_exit(load),
collection_count: 0,
}
}
pub fn continue_to_state(mut self, next: State) -> Self {
self.next_state = Some(next);
self
}
pub fn with_collection<A: AssetCollection>(mut self) -> Self {
self.load = self
.load
.with_system(start_loading::<State, A>.exclusive_system());
self.check = self
.check
.with_system(check_loading_state::<State, A>.exclusive_system());
self.collection_count += 1;
self
}
pub fn add_keys(mut self, mut keys: HashMap<String, String>) -> Self {
keys.drain().for_each(|(key, value)| {
self.keys.insert(key, value);
});
self
}
pub fn init_resource<A: FromWorld + Send + Sync + 'static>(mut self) -> Self {
self.post_process = self
.post_process
.with_system(init_resource::<A>.exclusive_system());
self
}
pub fn build(self, app: &mut App) {
let asset_loader_configuration = app
.world
.get_resource_mut::<AssetLoaderConfiguration<State>>();
let config = LoadingConfiguration {
next: self.next_state.clone(),
count: 0,
};
if let Some(mut asset_loader_configuration) = asset_loader_configuration {
asset_loader_configuration
.configuration
.insert(self.loading_state.clone(), config);
} else {
let mut asset_loader_configuration = AssetLoaderConfiguration::default();
asset_loader_configuration
.configuration
.insert(self.loading_state.clone(), config);
app.world.insert_resource(asset_loader_configuration);
}
app.init_resource::<AssetKeys>();
app.add_system_set(self.load)
.add_system_set(self.check)
.add_system_set(self.post_process);
}
}
#[cfg(feature = "render")]
#[doc = include_str!("../../README.md")]
#[cfg(doctest)]
struct ReadmeDoctests;