use core::str;
use bevy::{
asset::{AssetLoader, ErasedLoadedAsset},
platform::collections::HashMap,
prelude::*,
};
use crate::prelude::*;
mod bundle_entry;
mod bundle_value;
mod call_load_asset;
mod load_asset;
pub use bundle_entry::BundleEntry;
pub use bundle_value::BundleValue;
pub use call_load_asset::{
AssetHandleWrapper, CallLoadAssetFrom, CallLoadFromWrapper, call_load_from, load_handle_from,
};
pub use load_asset::LoadAssetFrom;
#[derive(Asset, TypePath)]
pub struct AssetsBundle {
assets: HashMap<String, ErasedLoadedAsset>,
}
impl std::fmt::Debug for AssetsBundle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AssetsBundle")
.field("assets", &self.assets.keys())
.finish()
}
}
impl AssetsBundle {
pub fn has_asset(&self, key: &str) -> bool {
self.assets.contains_key(key)
}
pub fn get_asset<'a, A: Asset>(&'a self, key: &str) -> Result<&'a A> {
let loaded_asset = self
.assets
.get(key)
.ok_or_else(|| Error::UnknownAssetKey { key: key.into() })?;
loaded_asset.get::<A>().ok_or_else(|| {
Error::InvalidAssetCast {
expected: A::short_type_path(),
got: loaded_asset.asset_type_name(),
}
.into()
})
}
}
#[derive(Default, TypePath)]
pub(crate) struct AssetsBundleLoader;
impl AssetLoader for AssetsBundleLoader {
type Asset = AssetsBundle;
type Settings = ();
type Error = Error;
async fn load(
&self,
reader: &mut dyn bevy::asset::io::Reader,
_: &Self::Settings,
load_context: &mut bevy::asset::LoadContext<'_>,
) -> std::result::Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let assets: HashMap<String, BundleEntry> = ron::from_str(str::from_utf8(&bytes)?)?;
call_load_from!(
AssetsBundle,
"<FILE>".to_string(),
BundleEntry::Bundle(assets),
load_context
)
.await
}
fn extensions(&self) -> &[&str] {
&["bundle.ron"]
}
}
#[cfg(test)]
mod tests {
use super::*;
use bevy::asset::LoadState;
use std::time::Duration;
fn is_loading(load_state: LoadState) -> bool {
matches!(load_state, LoadState::Loading)
}
fn check_is_loaded(load_state: LoadState) {
match load_state {
LoadState::Loaded => (),
LoadState::Failed(f) => panic!("Failed to load: {}", f),
_ => panic!("Not loaded."),
}
}
#[test]
fn test_bundle_asset_loading() {
let mut app = App::new();
app.add_plugins((MinimalPlugins, AssetPlugin::default()));
app.init_asset::<AssetsBundle>();
app.init_asset_loader::<AssetsBundleLoader>();
app.init_asset::<assets::Text>();
app.init_asset_loader::<assets::text::TextLoader>();
let handle: Handle<AssetsBundle> = app
.world()
.resource::<AssetServer>()
.load("test_relative/test.bundle.ron");
app.update();
while is_loading(
app.world()
.resource::<AssetServer>()
.get_load_state(&handle)
.unwrap(),
) {
app.update();
std::thread::sleep(Duration::from_secs(1));
}
check_is_loaded(
app.world()
.resource::<AssetServer>()
.get_load_state(&handle)
.unwrap(),
);
let test_bundle = app
.world()
.resource::<Assets<AssetsBundle>>()
.get(&handle)
.unwrap();
assert!(test_bundle.has_asset("hello"));
assert_eq!(
test_bundle
.get_asset::<assets::Text>("hello")
.unwrap()
.text_ref(),
"hello"
);
assert!(test_bundle.has_asset("world"));
assert_eq!(
test_bundle
.get_asset::<assets::Text>("world")
.unwrap()
.text_ref(),
"world"
);
assert!(test_bundle.has_asset("text"));
assert_eq!(
test_bundle
.get_asset::<assets::Text>("text")
.unwrap()
.text_ref(),
"Hello World!"
);
}
#[test]
fn test_bundle_with_bundle_asset_loading() {
let mut app = App::new();
app.add_plugins((MinimalPlugins, AssetPlugin::default()));
app.init_asset::<AssetsBundle>();
app.init_asset_loader::<AssetsBundleLoader>();
app.init_asset::<assets::Text>();
app.init_asset_loader::<assets::text::TextLoader>();
let handle: Handle<AssetsBundle> = app
.world()
.resource::<AssetServer>()
.load("test_bundle.bundle.ron");
app.update();
while is_loading(
app.world()
.resource::<AssetServer>()
.get_load_state(&handle)
.unwrap(),
) {
app.update();
std::thread::sleep(Duration::from_secs(1));
}
check_is_loaded(
app.world()
.resource::<AssetServer>()
.get_load_state(&handle)
.unwrap(),
);
let test_bundle = app
.world()
.resource::<Assets<AssetsBundle>>()
.get(&handle)
.unwrap();
assert!(test_bundle.has_asset("hello"));
assert_eq!(
test_bundle
.get_asset::<assets::Text>("hello")
.unwrap()
.text_ref(),
"hello"
);
assert!(test_bundle.has_asset("world"));
let test_bundle = test_bundle.get_asset::<AssetsBundle>("world").unwrap();
assert!(test_bundle.has_asset("hello"));
assert_eq!(
test_bundle
.get_asset::<assets::Text>("hello")
.unwrap()
.text_ref(),
"hello"
);
assert!(test_bundle.has_asset("world"));
assert_eq!(
test_bundle
.get_asset::<assets::Text>("world")
.unwrap()
.text_ref(),
"world"
);
assert!(test_bundle.has_asset("text"));
assert_eq!(
test_bundle
.get_asset::<assets::Text>("text")
.unwrap()
.text_ref(),
"Hello World!"
);
}
#[cfg(feature = "atlas_layout")]
#[test]
fn test_static_asset_atlast_layout_loading() {
let mut app = App::new();
app.add_plugins((MinimalPlugins, AssetPlugin::default()));
app.init_asset::<AssetsBundle>();
app.init_asset::<TextureAtlasLayout>();
app.init_asset_loader::<AssetsBundleLoader>();
app.init_asset_loader::<crate::assets::atlas_layout::TextureAtlasLayoutLoader>();
let handle: Handle<AssetsBundle> = app
.world()
.resource::<AssetServer>()
.load("with_atlas_layout.bundle.ron");
app.update();
while is_loading(
app.world()
.resource::<AssetServer>()
.get_load_state(&handle)
.unwrap(),
) {
app.update();
std::thread::sleep(Duration::from_secs(1));
}
let test_bundle = app
.world()
.resource::<Assets<AssetsBundle>>()
.get(&handle)
.unwrap();
assert!(test_bundle.has_asset("layout"));
let texture_atlas = test_bundle
.get_asset::<TextureAtlasLayout>("layout")
.unwrap();
assert_eq!(texture_atlas.size, UVec2::new(192, 297));
assert_eq!(
texture_atlas.textures,
vec![
URect::new(0, 0, 96, 99),
URect::new(96, 0, 192, 99),
URect::new(0, 99, 96, 198),
URect::new(96, 99, 192, 198),
URect::new(0, 198, 96, 297),
URect::new(96, 198, 192, 297),
]
);
}
}