Skip to content

Commit 4e8ae77

Browse files
committed
[assets] set LoadState properly and more testing!
1 parent 653c103 commit 4e8ae77

File tree

2 files changed

+156
-18
lines changed

2 files changed

+156
-18
lines changed

crates/bevy_asset/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,7 @@ js-sys = "0.3"
4545

4646
[target.'cfg(target_os = "android")'.dependencies]
4747
ndk-glue = { version = "0.2" }
48+
49+
[dev-dependencies]
50+
futures-lite = "1.4.0"
51+
tempfile = "3.2.0"

crates/bevy_asset/src/asset_server.rs

+152-18
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,6 @@ impl AssetServer {
230230
self.load_untyped(path).typed()
231231
}
232232

233-
// TODO: properly set failed LoadState in all failure cases
234233
async fn load_async(
235234
&self,
236235
asset_path: AssetPath<'_>,
@@ -272,15 +271,19 @@ impl AssetServer {
272271
source_info.version
273272
};
274273

274+
let set_asset_failed = || {
275+
let mut asset_sources = self.server.asset_sources.write();
276+
let source_info = asset_sources
277+
.get_mut(&asset_path_id.source_path_id())
278+
.expect("`AssetSource` should exist at this point.");
279+
source_info.load_state = LoadState::Failed;
280+
};
281+
275282
// load the asset bytes
276283
let bytes = match self.server.asset_io.load_path(asset_path.path()).await {
277284
Ok(bytes) => bytes,
278285
Err(err) => {
279-
let mut asset_sources = self.server.asset_sources.write();
280-
let source_info = asset_sources
281-
.get_mut(&asset_path_id.source_path_id())
282-
.expect("`AssetSource` should exist at this point.");
283-
source_info.load_state = LoadState::Failed;
286+
set_asset_failed();
284287
return Err(AssetServerError::AssetIoError(err));
285288
}
286289
};
@@ -293,10 +296,15 @@ impl AssetServer {
293296
version,
294297
&self.server.task_pool,
295298
);
296-
asset_loader
299+
300+
if let Err(err) = asset_loader
297301
.load(&bytes, &mut load_context)
298302
.await
299-
.map_err(AssetServerError::AssetLoaderError)?;
303+
.map_err(AssetServerError::AssetLoaderError)
304+
{
305+
set_asset_failed();
306+
return Err(err);
307+
}
300308

301309
// if version has changed since we loaded and grabbed a lock, return. theres is a newer
302310
// version being loaded
@@ -515,23 +523,35 @@ impl AssetServer {
515523
}
516524
}
517525

518-
pub fn free_unused_assets_system(asset_server: Res<AssetServer>) {
526+
fn free_unused_assets_system_impl(asset_server: &AssetServer) {
519527
asset_server.free_unused_assets();
520528
asset_server.mark_unused_assets();
521529
}
522530

531+
pub fn free_unused_assets_system(asset_server: Res<AssetServer>) {
532+
free_unused_assets_system_impl(&asset_server);
533+
}
534+
523535
#[cfg(test)]
524536
mod test {
525537
use super::*;
538+
use crate::loader::LoadedAsset;
539+
use bevy_reflect::TypeUuid;
526540
use bevy_utils::BoxedFuture;
541+
use tempfile::*;
542+
543+
#[derive(Debug, TypeUuid)]
544+
#[uuid = "a5189b72-0572-4290-a2e0-96f73a491c44"]
545+
struct PngAsset;
527546

528547
struct FakePngLoader;
529548
impl AssetLoader for FakePngLoader {
530549
fn load<'a>(
531550
&'a self,
532551
_: &'a [u8],
533-
_: &'a mut LoadContext,
552+
ctx: &'a mut LoadContext,
534553
) -> BoxedFuture<'a, Result<(), anyhow::Error>> {
554+
ctx.set_default_asset(LoadedAsset::new(PngAsset));
535555
Box::pin(async move { Ok(()) })
536556
}
537557

@@ -540,6 +560,21 @@ mod test {
540560
}
541561
}
542562

563+
struct FailingLoader;
564+
impl AssetLoader for FailingLoader {
565+
fn load<'a>(
566+
&'a self,
567+
_: &'a [u8],
568+
_: &'a mut LoadContext,
569+
) -> BoxedFuture<'a, Result<(), anyhow::Error>> {
570+
Box::pin(async { anyhow::bail!("failed") })
571+
}
572+
573+
fn extensions(&self) -> &[&str] {
574+
&["fail"]
575+
}
576+
}
577+
543578
struct FakeMultipleDotLoader;
544579
impl AssetLoader for FakeMultipleDotLoader {
545580
fn load<'a>(
@@ -555,7 +590,7 @@ mod test {
555590
}
556591
}
557592

558-
fn setup() -> AssetServer {
593+
fn setup(asset_path: impl AsRef<Path>) -> AssetServer {
559594
use crate::FileAssetIo;
560595

561596
let asset_server = AssetServer {
@@ -567,38 +602,39 @@ mod test {
567602
handle_to_path: Default::default(),
568603
asset_lifecycles: Default::default(),
569604
task_pool: Default::default(),
570-
asset_io: Box::new(FileAssetIo::new(&".")),
605+
asset_io: Box::new(FileAssetIo::new(asset_path)),
571606
}),
572607
};
573608
asset_server.add_loader::<FakePngLoader>(FakePngLoader);
609+
asset_server.add_loader::<FailingLoader>(FailingLoader);
574610
asset_server.add_loader::<FakeMultipleDotLoader>(FakeMultipleDotLoader);
575611
asset_server
576612
}
577613

578614
#[test]
579615
fn extensions() {
580-
let asset_server = setup();
616+
let asset_server = setup(".");
581617
let t = asset_server.get_path_asset_loader("test.png");
582618
assert_eq!(t.unwrap().extensions()[0], "png");
583619
}
584620

585621
#[test]
586622
fn case_insensitive_extensions() {
587-
let asset_server = setup();
623+
let asset_server = setup(".");
588624
let t = asset_server.get_path_asset_loader("test.PNG");
589625
assert_eq!(t.unwrap().extensions()[0], "png");
590626
}
591627

592628
#[test]
593629
fn no_loader() {
594-
let asset_server = setup();
630+
let asset_server = setup(".");
595631
let t = asset_server.get_path_asset_loader("test.pong");
596632
assert!(t.is_err());
597633
}
598634

599635
#[test]
600636
fn multiple_extensions_no_loader() {
601-
let asset_server = setup();
637+
let asset_server = setup(".");
602638

603639
assert!(
604640
match asset_server.get_path_asset_loader("test.v1.2.3.pong") {
@@ -633,15 +669,113 @@ mod test {
633669

634670
#[test]
635671
fn filename_with_dots() {
636-
let asset_server = setup();
672+
let asset_server = setup(".");
637673
let t = asset_server.get_path_asset_loader("test-v1.2.3.png");
638674
assert_eq!(t.unwrap().extensions()[0], "png");
639675
}
640676

641677
#[test]
642678
fn multiple_extensions() {
643-
let asset_server = setup();
679+
let asset_server = setup(".");
644680
let t = asset_server.get_path_asset_loader("test.test.png");
645681
assert_eq!(t.unwrap().extensions()[0], "test.png");
646682
}
683+
684+
fn create_dir_and_file(file: impl AsRef<Path>) -> TempDir {
685+
let asset_dir = tempdir().unwrap();
686+
std::fs::write(asset_dir.path().join(file), &[]).unwrap();
687+
asset_dir
688+
}
689+
690+
#[test]
691+
fn test_missing_loader() {
692+
let dir = create_dir_and_file("file.not-a-real-extension");
693+
let asset_server = setup(dir.path());
694+
695+
let path: AssetPath = "file.not-a-real-extension".into();
696+
let handle = asset_server.get_handle_untyped(path.get_id());
697+
698+
let err = futures_lite::future::block_on(asset_server.load_async(path.clone(), true))
699+
.unwrap_err();
700+
assert!(match err {
701+
AssetServerError::MissingAssetLoader { extensions } => {
702+
extensions == ["not-a-real-extension"]
703+
}
704+
_ => false,
705+
});
706+
707+
assert_eq!(asset_server.get_load_state(handle), LoadState::NotLoaded);
708+
}
709+
710+
#[test]
711+
fn test_invalid_asset_path() {
712+
let asset_server = setup(".");
713+
714+
let path: AssetPath = "an/invalid/path.png".into();
715+
let handle = asset_server.get_handle_untyped(path.get_id());
716+
717+
let err = futures_lite::future::block_on(asset_server.load_async(path.clone(), true))
718+
.unwrap_err();
719+
assert!(matches!(err, AssetServerError::AssetIoError(_)));
720+
721+
assert_eq!(asset_server.get_load_state(handle), LoadState::Failed);
722+
}
723+
724+
#[test]
725+
fn test_failing_loader() {
726+
let dir = create_dir_and_file("fake.fail");
727+
let asset_server = setup(dir.path());
728+
729+
let path: AssetPath = "fake.fail".into();
730+
let handle = asset_server.get_handle_untyped(path.get_id());
731+
732+
let err = futures_lite::future::block_on(asset_server.load_async(path.clone(), true))
733+
.unwrap_err();
734+
assert!(matches!(err, AssetServerError::AssetLoaderError(_)));
735+
736+
assert_eq!(asset_server.get_load_state(handle), LoadState::Failed);
737+
}
738+
739+
#[test]
740+
fn test_asset_lifecycle() {
741+
let dir = create_dir_and_file("fake.png");
742+
let asset_server = setup(dir.path());
743+
let mut assets = asset_server.register_asset_type::<PngAsset>();
744+
745+
let path: AssetPath = "fake.png".into();
746+
assert_eq!(
747+
LoadState::NotLoaded,
748+
asset_server.get_load_state(path.get_id())
749+
);
750+
751+
// load the asset
752+
let id =
753+
futures_lite::future::block_on(asset_server.load_async(path.clone(), true)).unwrap();
754+
let handle = asset_server.get_handle_untyped(id);
755+
756+
// asset is loading
757+
assert_eq!(LoadState::Loading, asset_server.get_load_state(id));
758+
759+
// mimics one full frame
760+
let tick = |server: &AssetServer, assets: &mut Assets<PngAsset>| {
761+
free_unused_assets_system_impl(server);
762+
server.update_asset_storage(assets);
763+
};
764+
765+
tick(&asset_server, &mut assets);
766+
// asset should exist and be loaded at this point
767+
assert_eq!(LoadState::Loaded, asset_server.get_load_state(id));
768+
assert!(assets.get(&handle).is_some());
769+
770+
// after dropping the handle, next call to `tick` will prepare the assets for removal.
771+
drop(handle);
772+
tick(&asset_server, &mut assets);
773+
assert_eq!(LoadState::Loaded, asset_server.get_load_state(id));
774+
assert!(assets.get(id).is_some());
775+
776+
// second call to tick will actually remove the asset.
777+
tick(&asset_server, &mut assets);
778+
assert_eq!(LoadState::NotLoaded, asset_server.get_load_state(id));
779+
assert!(assets.get(id).is_none());
780+
}
647781
}

0 commit comments

Comments
 (0)