@@ -205,6 +205,7 @@ impl AssetServer {
205
205
}
206
206
LoadState :: Failed => return LoadState :: Failed ,
207
207
LoadState :: NotLoaded => return LoadState :: NotLoaded ,
208
+ LoadState :: Unloaded => return LoadState :: Unloaded ,
208
209
} ,
209
210
HandleId :: Id ( _, _) => return LoadState :: NotLoaded ,
210
211
}
@@ -230,7 +231,6 @@ impl AssetServer {
230
231
self . load_untyped ( path) . typed ( )
231
232
}
232
233
233
- // TODO: properly set failed LoadState in all failure cases
234
234
async fn load_async (
235
235
& self ,
236
236
asset_path : AssetPath < ' _ > ,
@@ -272,15 +272,19 @@ impl AssetServer {
272
272
source_info. version
273
273
} ;
274
274
275
+ let set_asset_failed = || {
276
+ let mut asset_sources = self . server . asset_sources . write ( ) ;
277
+ let source_info = asset_sources
278
+ . get_mut ( & asset_path_id. source_path_id ( ) )
279
+ . expect ( "`AssetSource` should exist at this point." ) ;
280
+ source_info. load_state = LoadState :: Failed ;
281
+ } ;
282
+
275
283
// load the asset bytes
276
284
let bytes = match self . server . asset_io . load_path ( asset_path. path ( ) ) . await {
277
285
Ok ( bytes) => bytes,
278
286
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 ;
287
+ set_asset_failed ( ) ;
284
288
return Err ( AssetServerError :: AssetIoError ( err) ) ;
285
289
}
286
290
} ;
@@ -293,10 +297,15 @@ impl AssetServer {
293
297
version,
294
298
& self . server . task_pool ,
295
299
) ;
296
- asset_loader
300
+
301
+ if let Err ( err) = asset_loader
297
302
. load ( & bytes, & mut load_context)
298
303
. await
299
- . map_err ( AssetServerError :: AssetLoaderError ) ?;
304
+ . map_err ( AssetServerError :: AssetLoaderError )
305
+ {
306
+ set_asset_failed ( ) ;
307
+ return Err ( err) ;
308
+ }
300
309
301
310
// if version has changed since we loaded and grabbed a lock, return. theres is a newer
302
311
// version being loaded
@@ -500,9 +509,7 @@ impl AssetServer {
500
509
. get_or_insert_with ( || self . server . asset_sources . write ( ) ) ;
501
510
if let Some ( source_info) = asset_sources. get_mut ( & id. source_path_id ( ) ) {
502
511
source_info. committed_assets . remove ( & id. label_id ( ) ) ;
503
- if source_info. is_loaded ( ) {
504
- source_info. load_state = LoadState :: Loaded ;
505
- }
512
+ source_info. load_state = LoadState :: Unloaded ;
506
513
}
507
514
}
508
515
assets. remove ( handle_id) ;
@@ -516,23 +523,35 @@ impl AssetServer {
516
523
}
517
524
}
518
525
519
- pub fn free_unused_assets_system ( asset_server : Res < AssetServer > ) {
526
+ fn free_unused_assets_system_impl ( asset_server : & AssetServer ) {
520
527
asset_server. free_unused_assets ( ) ;
521
528
asset_server. mark_unused_assets ( ) ;
522
529
}
523
530
531
+ pub fn free_unused_assets_system ( asset_server : Res < AssetServer > ) {
532
+ free_unused_assets_system_impl ( & asset_server) ;
533
+ }
534
+
524
535
#[ cfg( test) ]
525
536
mod test {
526
537
use super :: * ;
538
+ use crate :: { loader:: LoadedAsset , update_asset_storage_system} ;
539
+ use bevy_ecs:: prelude:: * ;
540
+ use bevy_reflect:: TypeUuid ;
527
541
use bevy_utils:: BoxedFuture ;
528
542
543
+ #[ derive( Debug , TypeUuid ) ]
544
+ #[ uuid = "a5189b72-0572-4290-a2e0-96f73a491c44" ]
545
+ struct PngAsset ;
546
+
529
547
struct FakePngLoader ;
530
548
impl AssetLoader for FakePngLoader {
531
549
fn load < ' a > (
532
550
& ' a self ,
533
551
_: & ' a [ u8 ] ,
534
- _ : & ' a mut LoadContext ,
552
+ ctx : & ' a mut LoadContext ,
535
553
) -> BoxedFuture < ' a , Result < ( ) , anyhow:: Error > > {
554
+ ctx. set_default_asset ( LoadedAsset :: new ( PngAsset ) ) ;
536
555
Box :: pin ( async move { Ok ( ( ) ) } )
537
556
}
538
557
@@ -541,6 +560,21 @@ mod test {
541
560
}
542
561
}
543
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
+
544
578
struct FakeMultipleDotLoader ;
545
579
impl AssetLoader for FakeMultipleDotLoader {
546
580
fn load < ' a > (
@@ -556,10 +590,10 @@ mod test {
556
590
}
557
591
}
558
592
559
- fn setup ( ) -> AssetServer {
593
+ fn setup ( asset_path : impl AsRef < Path > ) -> AssetServer {
560
594
use crate :: FileAssetIo ;
561
595
562
- let asset_server = AssetServer {
596
+ AssetServer {
563
597
server : Arc :: new ( AssetServerInternal {
564
598
loaders : Default :: default ( ) ,
565
599
extension_to_loader_index : Default :: default ( ) ,
@@ -568,38 +602,39 @@ mod test {
568
602
handle_to_path : Default :: default ( ) ,
569
603
asset_lifecycles : Default :: default ( ) ,
570
604
task_pool : Default :: default ( ) ,
571
- asset_io : Box :: new ( FileAssetIo :: new ( & "." ) ) ,
605
+ asset_io : Box :: new ( FileAssetIo :: new ( asset_path ) ) ,
572
606
} ) ,
573
- } ;
574
- asset_server. add_loader :: < FakePngLoader > ( FakePngLoader ) ;
575
- asset_server. add_loader :: < FakeMultipleDotLoader > ( FakeMultipleDotLoader ) ;
576
- asset_server
607
+ }
577
608
}
578
609
579
610
#[ test]
580
611
fn extensions ( ) {
581
- let asset_server = setup ( ) ;
612
+ let asset_server = setup ( "." ) ;
613
+ asset_server. add_loader ( FakePngLoader ) ;
614
+
582
615
let t = asset_server. get_path_asset_loader ( "test.png" ) ;
583
616
assert_eq ! ( t. unwrap( ) . extensions( ) [ 0 ] , "png" ) ;
584
617
}
585
618
586
619
#[ test]
587
620
fn case_insensitive_extensions ( ) {
588
- let asset_server = setup ( ) ;
621
+ let asset_server = setup ( "." ) ;
622
+ asset_server. add_loader ( FakePngLoader ) ;
623
+
589
624
let t = asset_server. get_path_asset_loader ( "test.PNG" ) ;
590
625
assert_eq ! ( t. unwrap( ) . extensions( ) [ 0 ] , "png" ) ;
591
626
}
592
627
593
628
#[ test]
594
629
fn no_loader ( ) {
595
- let asset_server = setup ( ) ;
630
+ let asset_server = setup ( "." ) ;
596
631
let t = asset_server. get_path_asset_loader ( "test.pong" ) ;
597
632
assert ! ( t. is_err( ) ) ;
598
633
}
599
634
600
635
#[ test]
601
636
fn multiple_extensions_no_loader ( ) {
602
- let asset_server = setup ( ) ;
637
+ let asset_server = setup ( "." ) ;
603
638
604
639
assert ! (
605
640
match asset_server. get_path_asset_loader( "test.v1.2.3.pong" ) {
@@ -634,15 +669,158 @@ mod test {
634
669
635
670
#[ test]
636
671
fn filename_with_dots ( ) {
637
- let asset_server = setup ( ) ;
672
+ let asset_server = setup ( "." ) ;
673
+ asset_server. add_loader ( FakePngLoader ) ;
674
+
638
675
let t = asset_server. get_path_asset_loader ( "test-v1.2.3.png" ) ;
639
676
assert_eq ! ( t. unwrap( ) . extensions( ) [ 0 ] , "png" ) ;
640
677
}
641
678
642
679
#[ test]
643
680
fn multiple_extensions ( ) {
644
- let asset_server = setup ( ) ;
681
+ let asset_server = setup ( "." ) ;
682
+ asset_server. add_loader ( FakeMultipleDotLoader ) ;
683
+
645
684
let t = asset_server. get_path_asset_loader ( "test.test.png" ) ;
646
685
assert_eq ! ( t. unwrap( ) . extensions( ) [ 0 ] , "test.png" ) ;
647
686
}
687
+
688
+ fn create_dir_and_file ( file : impl AsRef < Path > ) -> tempfile:: TempDir {
689
+ let asset_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
690
+ std:: fs:: write ( asset_dir. path ( ) . join ( file) , & [ ] ) . unwrap ( ) ;
691
+ asset_dir
692
+ }
693
+
694
+ #[ test]
695
+ fn test_missing_loader ( ) {
696
+ let dir = create_dir_and_file ( "file.not-a-real-extension" ) ;
697
+ let asset_server = setup ( dir. path ( ) ) ;
698
+
699
+ let path: AssetPath = "file.not-a-real-extension" . into ( ) ;
700
+ let handle = asset_server. get_handle_untyped ( path. get_id ( ) ) ;
701
+
702
+ let err = futures_lite:: future:: block_on ( asset_server. load_async ( path. clone ( ) , true ) )
703
+ . unwrap_err ( ) ;
704
+ assert ! ( match err {
705
+ AssetServerError :: MissingAssetLoader { extensions } => {
706
+ extensions == [ "not-a-real-extension" ]
707
+ }
708
+ _ => false ,
709
+ } ) ;
710
+
711
+ assert_eq ! ( asset_server. get_load_state( handle) , LoadState :: NotLoaded ) ;
712
+ }
713
+
714
+ #[ test]
715
+ fn test_invalid_asset_path ( ) {
716
+ let asset_server = setup ( "." ) ;
717
+ asset_server. add_loader ( FakePngLoader ) ;
718
+
719
+ let path: AssetPath = "an/invalid/path.png" . into ( ) ;
720
+ let handle = asset_server. get_handle_untyped ( path. get_id ( ) ) ;
721
+
722
+ let err = futures_lite:: future:: block_on ( asset_server. load_async ( path. clone ( ) , true ) )
723
+ . unwrap_err ( ) ;
724
+ assert ! ( matches!( err, AssetServerError :: AssetIoError ( _) ) ) ;
725
+
726
+ assert_eq ! ( asset_server. get_load_state( handle) , LoadState :: Failed ) ;
727
+ }
728
+
729
+ #[ test]
730
+ fn test_failing_loader ( ) {
731
+ let dir = create_dir_and_file ( "fake.fail" ) ;
732
+ let asset_server = setup ( dir. path ( ) ) ;
733
+ asset_server. add_loader ( FailingLoader ) ;
734
+
735
+ let path: AssetPath = "fake.fail" . into ( ) ;
736
+ let handle = asset_server. get_handle_untyped ( path. get_id ( ) ) ;
737
+
738
+ let err = futures_lite:: future:: block_on ( asset_server. load_async ( path. clone ( ) , true ) )
739
+ . unwrap_err ( ) ;
740
+ assert ! ( matches!( err, AssetServerError :: AssetLoaderError ( _) ) ) ;
741
+
742
+ assert_eq ! ( asset_server. get_load_state( handle) , LoadState :: Failed ) ;
743
+ }
744
+
745
+ #[ test]
746
+ fn test_asset_lifecycle ( ) {
747
+ let dir = create_dir_and_file ( "fake.png" ) ;
748
+ let asset_server = setup ( dir. path ( ) ) ;
749
+ asset_server. add_loader ( FakePngLoader ) ;
750
+ let assets = asset_server. register_asset_type :: < PngAsset > ( ) ;
751
+
752
+ let mut world = World :: new ( ) ;
753
+ world. insert_resource ( assets) ;
754
+ world. insert_resource ( asset_server) ;
755
+
756
+ let mut tick = {
757
+ let mut free_unused_assets_system = free_unused_assets_system. system ( ) ;
758
+ free_unused_assets_system. initialize ( & mut world) ;
759
+ let mut update_asset_storage_system = update_asset_storage_system :: < PngAsset > . system ( ) ;
760
+ update_asset_storage_system. initialize ( & mut world) ;
761
+
762
+ move |world : & mut World | {
763
+ free_unused_assets_system. run ( ( ) , world) ;
764
+ update_asset_storage_system. run ( ( ) , world) ;
765
+ }
766
+ } ;
767
+
768
+ fn load_asset ( path : AssetPath , world : & World ) -> HandleUntyped {
769
+ let asset_server = world. get_resource :: < AssetServer > ( ) . unwrap ( ) ;
770
+ let id = futures_lite:: future:: block_on ( asset_server. load_async ( path. clone ( ) , true ) )
771
+ . unwrap ( ) ;
772
+ asset_server. get_handle_untyped ( id)
773
+ }
774
+
775
+ fn get_asset ( id : impl Into < HandleId > , world : & World ) -> Option < & PngAsset > {
776
+ world
777
+ . get_resource :: < Assets < PngAsset > > ( )
778
+ . unwrap ( )
779
+ . get ( id. into ( ) )
780
+ }
781
+
782
+ fn get_load_state ( id : impl Into < HandleId > , world : & World ) -> LoadState {
783
+ world
784
+ . get_resource :: < AssetServer > ( )
785
+ . unwrap ( )
786
+ . get_load_state ( id. into ( ) )
787
+ }
788
+
789
+ // ---
790
+ // Start of the actual lifecycle test
791
+ // ---
792
+
793
+ let path: AssetPath = "fake.png" . into ( ) ;
794
+ assert_eq ! ( LoadState :: NotLoaded , get_load_state( path. get_id( ) , & world) ) ;
795
+
796
+ // load the asset
797
+ let handle = load_asset ( path. clone ( ) , & world) ;
798
+ let weak_handle = handle. clone_weak ( ) ;
799
+
800
+ // asset is loading
801
+ assert_eq ! ( LoadState :: Loading , get_load_state( & handle, & world) ) ;
802
+
803
+ tick ( & mut world) ;
804
+ // asset should exist and be loaded at this point
805
+ assert_eq ! ( LoadState :: Loaded , get_load_state( & handle, & world) ) ;
806
+ assert ! ( get_asset( & handle, & world) . is_some( ) ) ;
807
+
808
+ // after dropping the handle, next call to `tick` will prepare the assets for removal.
809
+ drop ( handle) ;
810
+ tick ( & mut world) ;
811
+ assert_eq ! ( LoadState :: Loaded , get_load_state( & weak_handle, & world) ) ;
812
+ assert ! ( get_asset( & weak_handle, & world) . is_some( ) ) ;
813
+
814
+ // second call to tick will actually remove the asset.
815
+ tick ( & mut world) ;
816
+ assert_eq ! ( LoadState :: Unloaded , get_load_state( & weak_handle, & world) ) ;
817
+ assert ! ( get_asset( & weak_handle, & world) . is_none( ) ) ;
818
+
819
+ // finally, reload the asset
820
+ let handle = load_asset ( path. clone ( ) , & world) ;
821
+ assert_eq ! ( LoadState :: Loading , get_load_state( & handle, & world) ) ;
822
+ tick ( & mut world) ;
823
+ assert_eq ! ( LoadState :: Loaded , get_load_state( & handle, & world) ) ;
824
+ assert ! ( get_asset( & handle, & world) . is_some( ) ) ;
825
+ }
648
826
}
0 commit comments