@@ -230,7 +230,6 @@ impl AssetServer {
230
230
self . load_untyped ( path) . typed ( )
231
231
}
232
232
233
- // TODO: properly set failed LoadState in all failure cases
234
233
async fn load_async (
235
234
& self ,
236
235
asset_path : AssetPath < ' _ > ,
@@ -272,15 +271,19 @@ impl AssetServer {
272
271
source_info. version
273
272
} ;
274
273
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
+
275
282
// load the asset bytes
276
283
let bytes = match self . server . asset_io . load_path ( asset_path. path ( ) ) . await {
277
284
Ok ( bytes) => bytes,
278
285
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 ( ) ;
284
287
return Err ( AssetServerError :: AssetIoError ( err) ) ;
285
288
}
286
289
} ;
@@ -293,10 +296,15 @@ impl AssetServer {
293
296
version,
294
297
& self . server . task_pool ,
295
298
) ;
296
- asset_loader
299
+
300
+ if let Err ( err) = asset_loader
297
301
. load ( & bytes, & mut load_context)
298
302
. await
299
- . map_err ( AssetServerError :: AssetLoaderError ) ?;
303
+ . map_err ( AssetServerError :: AssetLoaderError )
304
+ {
305
+ set_asset_failed ( ) ;
306
+ return Err ( err) ;
307
+ }
300
308
301
309
// if version has changed since we loaded and grabbed a lock, return. theres is a newer
302
310
// version being loaded
@@ -515,23 +523,35 @@ impl AssetServer {
515
523
}
516
524
}
517
525
518
- pub fn free_unused_assets_system ( asset_server : Res < AssetServer > ) {
526
+ fn free_unused_assets_system_impl ( asset_server : & AssetServer ) {
519
527
asset_server. free_unused_assets ( ) ;
520
528
asset_server. mark_unused_assets ( ) ;
521
529
}
522
530
531
+ pub fn free_unused_assets_system ( asset_server : Res < AssetServer > ) {
532
+ free_unused_assets_system_impl ( & asset_server) ;
533
+ }
534
+
523
535
#[ cfg( test) ]
524
536
mod test {
525
537
use super :: * ;
538
+ use crate :: loader:: LoadedAsset ;
539
+ use bevy_reflect:: TypeUuid ;
526
540
use bevy_utils:: BoxedFuture ;
541
+ use tempfile:: * ;
542
+
543
+ #[ derive( Debug , TypeUuid ) ]
544
+ #[ uuid = "a5189b72-0572-4290-a2e0-96f73a491c44" ]
545
+ struct PngAsset ;
527
546
528
547
struct FakePngLoader ;
529
548
impl AssetLoader for FakePngLoader {
530
549
fn load < ' a > (
531
550
& ' a self ,
532
551
_: & ' a [ u8 ] ,
533
- _ : & ' a mut LoadContext ,
552
+ ctx : & ' a mut LoadContext ,
534
553
) -> BoxedFuture < ' a , Result < ( ) , anyhow:: Error > > {
554
+ ctx. set_default_asset ( LoadedAsset :: new ( PngAsset ) ) ;
535
555
Box :: pin ( async move { Ok ( ( ) ) } )
536
556
}
537
557
@@ -540,6 +560,21 @@ mod test {
540
560
}
541
561
}
542
562
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
+
543
578
struct FakeMultipleDotLoader ;
544
579
impl AssetLoader for FakeMultipleDotLoader {
545
580
fn load < ' a > (
@@ -555,7 +590,7 @@ mod test {
555
590
}
556
591
}
557
592
558
- fn setup ( ) -> AssetServer {
593
+ fn setup ( asset_path : impl AsRef < Path > ) -> AssetServer {
559
594
use crate :: FileAssetIo ;
560
595
561
596
let asset_server = AssetServer {
@@ -567,38 +602,39 @@ mod test {
567
602
handle_to_path : Default :: default ( ) ,
568
603
asset_lifecycles : Default :: default ( ) ,
569
604
task_pool : Default :: default ( ) ,
570
- asset_io : Box :: new ( FileAssetIo :: new ( & "." ) ) ,
605
+ asset_io : Box :: new ( FileAssetIo :: new ( asset_path ) ) ,
571
606
} ) ,
572
607
} ;
573
608
asset_server. add_loader :: < FakePngLoader > ( FakePngLoader ) ;
609
+ asset_server. add_loader :: < FailingLoader > ( FailingLoader ) ;
574
610
asset_server. add_loader :: < FakeMultipleDotLoader > ( FakeMultipleDotLoader ) ;
575
611
asset_server
576
612
}
577
613
578
614
#[ test]
579
615
fn extensions ( ) {
580
- let asset_server = setup ( ) ;
616
+ let asset_server = setup ( "." ) ;
581
617
let t = asset_server. get_path_asset_loader ( "test.png" ) ;
582
618
assert_eq ! ( t. unwrap( ) . extensions( ) [ 0 ] , "png" ) ;
583
619
}
584
620
585
621
#[ test]
586
622
fn case_insensitive_extensions ( ) {
587
- let asset_server = setup ( ) ;
623
+ let asset_server = setup ( "." ) ;
588
624
let t = asset_server. get_path_asset_loader ( "test.PNG" ) ;
589
625
assert_eq ! ( t. unwrap( ) . extensions( ) [ 0 ] , "png" ) ;
590
626
}
591
627
592
628
#[ test]
593
629
fn no_loader ( ) {
594
- let asset_server = setup ( ) ;
630
+ let asset_server = setup ( "." ) ;
595
631
let t = asset_server. get_path_asset_loader ( "test.pong" ) ;
596
632
assert ! ( t. is_err( ) ) ;
597
633
}
598
634
599
635
#[ test]
600
636
fn multiple_extensions_no_loader ( ) {
601
- let asset_server = setup ( ) ;
637
+ let asset_server = setup ( "." ) ;
602
638
603
639
assert ! (
604
640
match asset_server. get_path_asset_loader( "test.v1.2.3.pong" ) {
@@ -633,15 +669,113 @@ mod test {
633
669
634
670
#[ test]
635
671
fn filename_with_dots ( ) {
636
- let asset_server = setup ( ) ;
672
+ let asset_server = setup ( "." ) ;
637
673
let t = asset_server. get_path_asset_loader ( "test-v1.2.3.png" ) ;
638
674
assert_eq ! ( t. unwrap( ) . extensions( ) [ 0 ] , "png" ) ;
639
675
}
640
676
641
677
#[ test]
642
678
fn multiple_extensions ( ) {
643
- let asset_server = setup ( ) ;
679
+ let asset_server = setup ( "." ) ;
644
680
let t = asset_server. get_path_asset_loader ( "test.test.png" ) ;
645
681
assert_eq ! ( t. unwrap( ) . extensions( ) [ 0 ] , "test.png" ) ;
646
682
}
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
+ }
647
781
}
0 commit comments