@@ -25,7 +25,7 @@ use bevy_utils::{
25
25
tracing:: { error, info} ,
26
26
HashSet ,
27
27
} ;
28
- use core:: { any:: TypeId , future:: Future , panic:: AssertUnwindSafe } ;
28
+ use core:: { any:: TypeId , future:: Future , panic:: AssertUnwindSafe , task :: Poll } ;
29
29
use crossbeam_channel:: { Receiver , Sender } ;
30
30
use derive_more:: derive:: { Display , Error , From } ;
31
31
use either:: Either ;
@@ -413,7 +413,7 @@ impl AssetServer {
413
413
& self ,
414
414
handle : UntypedHandle ,
415
415
path : AssetPath < ' static > ,
416
- mut infos : RwLockWriteGuard < AssetInfos > ,
416
+ infos : RwLockWriteGuard < AssetInfos > ,
417
417
guard : G ,
418
418
) {
419
419
// drop the lock on `AssetInfos` before spawning a task that may block on it in single-threaded
@@ -433,7 +433,10 @@ impl AssetServer {
433
433
} ) ;
434
434
435
435
#[ cfg( not( any( target_arch = "wasm32" , not( feature = "multi_threaded" ) ) ) ) ]
436
- infos. pending_tasks . insert ( handle. id ( ) , task) ;
436
+ {
437
+ let mut infos = infos;
438
+ infos. pending_tasks . insert ( handle. id ( ) , task) ;
439
+ }
437
440
438
441
#[ cfg( any( target_arch = "wasm32" , not( feature = "multi_threaded" ) ) ) ]
439
442
task. detach ( ) ;
@@ -1336,6 +1339,132 @@ impl AssetServer {
1336
1339
} )
1337
1340
} )
1338
1341
}
1342
+
1343
+ /// Returns a future that will suspend until the specified asset and its dependencies finish
1344
+ /// loading.
1345
+ ///
1346
+ /// # Errors
1347
+ ///
1348
+ /// This will return an error if the asset or any of its dependencies fail to load,
1349
+ /// or if the asset has not been queued up to be loaded.
1350
+ pub async fn wait_for_asset < A : Asset > (
1351
+ & self ,
1352
+ // NOTE: We take a reference to a handle so we know it will outlive the future,
1353
+ // which ensures the handle won't be dropped while waiting for the asset.
1354
+ handle : & Handle < A > ,
1355
+ ) -> Result < ( ) , WaitForAssetError > {
1356
+ self . wait_for_asset_id ( handle. id ( ) . untyped ( ) ) . await
1357
+ }
1358
+
1359
+ /// Returns a future that will suspend until the specified asset and its dependencies finish
1360
+ /// loading.
1361
+ ///
1362
+ /// # Errors
1363
+ ///
1364
+ /// This will return an error if the asset or any of its dependencies fail to load,
1365
+ /// or if the asset has not been queued up to be loaded.
1366
+ pub async fn wait_for_asset_untyped (
1367
+ & self ,
1368
+ // NOTE: We take a reference to a handle so we know it will outlive the future,
1369
+ // which ensures the handle won't be dropped while waiting for the asset.
1370
+ handle : & UntypedHandle ,
1371
+ ) -> Result < ( ) , WaitForAssetError > {
1372
+ self . wait_for_asset_id ( handle. id ( ) ) . await
1373
+ }
1374
+
1375
+ /// Returns a future that will suspend until the specified asset and its dependencies finish
1376
+ /// loading.
1377
+ ///
1378
+ /// Note that since an asset ID does not count as a reference to the asset,
1379
+ /// the future returned from this method will *not* keep the asset alive.
1380
+ /// This may lead to the asset unexpectedly being dropped while you are waiting for it to
1381
+ /// finish loading.
1382
+ ///
1383
+ /// When calling this method, make sure a strong handle is stored elsewhere to prevent the
1384
+ /// asset from being dropped.
1385
+ /// If you have access to an asset's strong [`Handle`], you should prefer to call
1386
+ /// [`AssetServer::wait_for_asset`]
1387
+ /// or [`wait_for_assest_untyped`](Self::wait_for_asset_untyped) to ensure the asset finishes
1388
+ /// loading.
1389
+ ///
1390
+ /// # Errors
1391
+ ///
1392
+ /// This will return an error if the asset or any of its dependencies fail to load,
1393
+ /// or if the asset has not been queued up to be loaded.
1394
+ pub async fn wait_for_asset_id (
1395
+ & self ,
1396
+ id : impl Into < UntypedAssetId > ,
1397
+ ) -> Result < ( ) , WaitForAssetError > {
1398
+ let id = id. into ( ) ;
1399
+ core:: future:: poll_fn ( move |cx| self . wait_for_asset_id_poll_fn ( cx, id) ) . await
1400
+ }
1401
+
1402
+ /// Used by [`wait_for_asset_id`](AssetServer::wait_for_asset_id) in [`poll_fn`](core::future::poll_fn).
1403
+ fn wait_for_asset_id_poll_fn (
1404
+ & self ,
1405
+ cx : & mut core:: task:: Context < ' _ > ,
1406
+ id : UntypedAssetId ,
1407
+ ) -> Poll < Result < ( ) , WaitForAssetError > > {
1408
+ let infos = self . data . infos . read ( ) ;
1409
+
1410
+ let Some ( info) = infos. get ( id) else {
1411
+ return Poll :: Ready ( Err ( WaitForAssetError :: NotLoaded ) ) ;
1412
+ } ;
1413
+
1414
+ match ( & info. load_state , & info. rec_dep_load_state ) {
1415
+ ( LoadState :: Loaded , RecursiveDependencyLoadState :: Loaded ) => Poll :: Ready ( Ok ( ( ) ) ) ,
1416
+ // Return an error immediately if the asset is not in the process of loading
1417
+ ( LoadState :: NotLoaded , _) => Poll :: Ready ( Err ( WaitForAssetError :: NotLoaded ) ) ,
1418
+ // If the asset is loading, leave our waker behind
1419
+ ( LoadState :: Loading , _)
1420
+ | ( _, RecursiveDependencyLoadState :: Loading )
1421
+ | ( LoadState :: Loaded , RecursiveDependencyLoadState :: NotLoaded ) => {
1422
+ // Check if our waker is already there
1423
+ let has_waker = info
1424
+ . waiting_tasks
1425
+ . iter ( )
1426
+ . any ( |waker| waker. will_wake ( cx. waker ( ) ) ) ;
1427
+
1428
+ if has_waker {
1429
+ return Poll :: Pending ;
1430
+ }
1431
+
1432
+ let mut infos = {
1433
+ // Must drop read-only guard to acquire write guard
1434
+ drop ( infos) ;
1435
+ self . data . infos . write ( )
1436
+ } ;
1437
+
1438
+ let Some ( info) = infos. get_mut ( id) else {
1439
+ return Poll :: Ready ( Err ( WaitForAssetError :: NotLoaded ) ) ;
1440
+ } ;
1441
+
1442
+ // If the load state changed while reacquiring the lock, immediately
1443
+ // reawaken the task
1444
+ let is_loading = matches ! (
1445
+ ( & info. load_state, & info. rec_dep_load_state) ,
1446
+ ( LoadState :: Loading , _)
1447
+ | ( _, RecursiveDependencyLoadState :: Loading )
1448
+ | ( LoadState :: Loaded , RecursiveDependencyLoadState :: NotLoaded )
1449
+ ) ;
1450
+
1451
+ if !is_loading {
1452
+ cx. waker ( ) . wake_by_ref ( ) ;
1453
+ } else {
1454
+ // Leave our waker behind
1455
+ info. waiting_tasks . push ( cx. waker ( ) . clone ( ) ) ;
1456
+ }
1457
+
1458
+ Poll :: Pending
1459
+ }
1460
+ ( LoadState :: Failed ( error) , _) => {
1461
+ Poll :: Ready ( Err ( WaitForAssetError :: Failed ( error. clone ( ) ) ) )
1462
+ }
1463
+ ( _, RecursiveDependencyLoadState :: Failed ( error) ) => {
1464
+ Poll :: Ready ( Err ( WaitForAssetError :: DependencyFailed ( error. clone ( ) ) ) )
1465
+ }
1466
+ }
1467
+ }
1339
1468
}
1340
1469
1341
1470
/// A system that manages internal [`AssetServer`] events, such as finalizing asset loads.
@@ -1359,6 +1488,11 @@ pub fn handle_internal_asset_events(world: &mut World) {
1359
1488
. get ( & id. type_id ( ) )
1360
1489
. expect ( "Asset event sender should exist" ) ;
1361
1490
sender ( world, id) ;
1491
+ if let Some ( info) = infos. get_mut ( id) {
1492
+ for waker in info. waiting_tasks . drain ( ..) {
1493
+ waker. wake ( ) ;
1494
+ }
1495
+ }
1362
1496
}
1363
1497
InternalAssetEvent :: Failed { id, path, error } => {
1364
1498
infos. process_asset_fail ( id, error. clone ( ) ) ;
@@ -1710,3 +1844,12 @@ impl core::fmt::Debug for AssetServer {
1710
1844
/// This is appended to asset sources when loading a [`LoadedUntypedAsset`]. This provides a unique
1711
1845
/// source for a given [`AssetPath`].
1712
1846
const UNTYPED_SOURCE_SUFFIX : & str = "--untyped" ;
1847
+
1848
+ /// An error when attempting to wait asynchronously for an [`Asset`] to load.
1849
+ #[ derive( Error , Debug , Clone , Display ) ]
1850
+ pub enum WaitForAssetError {
1851
+ #[ display( "tried to wait for an asset that is not being loaded" ) ]
1852
+ NotLoaded ,
1853
+ Failed ( Arc < AssetLoadError > ) ,
1854
+ DependencyFailed ( Arc < AssetLoadError > ) ,
1855
+ }
0 commit comments