bevy_asset/server/
mod.rs

1mod info;
2mod loaders;
3
4use crate::{
5    folder::LoadedFolder,
6    io::{
7        AssetReaderError, AssetSource, AssetSourceEvent, AssetSourceId, AssetSources,
8        AssetWriterError, ErasedAssetReader, MissingAssetSourceError, MissingAssetWriterError,
9        MissingProcessedAssetReaderError, Reader,
10    },
11    loader::{AssetLoader, ErasedAssetLoader, LoadContext, LoadedAsset},
12    meta::{
13        loader_settings_meta_transform, AssetActionMinimal, AssetMetaDyn, AssetMetaMinimal,
14        MetaTransform, Settings,
15    },
16    path::AssetPath,
17    Asset, AssetEvent, AssetHandleProvider, AssetId, AssetIndex, AssetLoadFailedEvent,
18    AssetMetaCheck, Assets, DeserializeMetaError, ErasedAssetIndex, ErasedLoadedAsset, Handle,
19    LoadedUntypedAsset, UnapprovedPathMode, UntypedAssetId, UntypedAssetLoadFailedEvent,
20    UntypedHandle,
21};
22use alloc::{borrow::ToOwned, boxed::Box, vec, vec::Vec};
23use alloc::{
24    format,
25    string::{String, ToString},
26    sync::Arc,
27};
28use atomicow::CowArc;
29use bevy_diagnostic::{DiagnosticPath, Diagnostics};
30use bevy_ecs::prelude::*;
31use bevy_platform::{
32    collections::HashSet,
33    sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
34};
35use bevy_tasks::IoTaskPool;
36use core::{any::TypeId, future::Future, panic::AssertUnwindSafe, task::Poll};
37use crossbeam_channel::{Receiver, Sender};
38use either::Either;
39use futures_lite::{FutureExt, StreamExt};
40use info::*;
41use loaders::*;
42use std::path::{Path, PathBuf};
43use thiserror::Error;
44use tracing::{error, info};
45
46/// Loads and tracks the state of [`Asset`] values from a configured [`AssetReader`](crate::io::AssetReader).
47/// This can be used to kick off new asset loads and retrieve their current load states.
48///
49/// The general process to load an asset is:
50/// 1. Initialize a new [`Asset`] type with the [`AssetServer`] via [`AssetApp::init_asset`], which
51///    will internally call [`AssetServer::register_asset`] and set up related ECS [`Assets`]
52///    storage and systems.
53/// 2. Register one or more [`AssetLoader`]s for that asset with [`AssetApp::init_asset_loader`]
54/// 3. Add the asset to your asset folder (defaults to `assets`).
55/// 4. Call [`AssetServer::load`] with a path to your asset.
56///
57/// [`AssetServer`] can be cloned. It is backed by an [`Arc`] so clones will share state. Clones can be freely used in parallel.
58///
59/// [`AssetApp::init_asset`]: crate::AssetApp::init_asset
60/// [`AssetApp::init_asset_loader`]: crate::AssetApp::init_asset_loader
61#[derive(Resource, Clone)]
62pub struct AssetServer {
63    pub(crate) data: Arc<AssetServerData>,
64}
65
66/// Internal data used by [`AssetServer`]. This is intended to be used from within an [`Arc`].
67pub(crate) struct AssetServerData {
68    pub(crate) infos: RwLock<AssetInfos>,
69    pub(crate) loaders: Arc<RwLock<AssetLoaders>>,
70    asset_event_sender: Sender<InternalAssetEvent>,
71    asset_event_receiver: Receiver<InternalAssetEvent>,
72    sources: Arc<AssetSources>,
73    mode: AssetServerMode,
74    meta_check: AssetMetaCheck,
75    unapproved_path_mode: UnapprovedPathMode,
76}
77
78/// The "asset mode" the server is currently in.
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum AssetServerMode {
81    /// This server loads unprocessed assets.
82    Unprocessed,
83    /// This server loads processed assets.
84    Processed,
85}
86
87impl AssetServer {
88    /// The number of loads that have been started by the server.
89    pub const STARTED_LOAD_COUNT: DiagnosticPath = DiagnosticPath::const_new("started_load_count");
90
91    /// Create a new instance of [`AssetServer`]. If `watch_for_changes` is true, the [`AssetReader`](crate::io::AssetReader) storage will watch for changes to
92    /// asset sources and hot-reload them.
93    pub fn new(
94        sources: Arc<AssetSources>,
95        mode: AssetServerMode,
96        watching_for_changes: bool,
97        unapproved_path_mode: UnapprovedPathMode,
98    ) -> Self {
99        Self::new_with_loaders(
100            sources,
101            Default::default(),
102            mode,
103            AssetMetaCheck::Always,
104            watching_for_changes,
105            unapproved_path_mode,
106        )
107    }
108
109    /// Create a new instance of [`AssetServer`]. If `watch_for_changes` is true, the [`AssetReader`](crate::io::AssetReader) storage will watch for changes to
110    /// asset sources and hot-reload them.
111    pub fn new_with_meta_check(
112        sources: Arc<AssetSources>,
113        mode: AssetServerMode,
114        meta_check: AssetMetaCheck,
115        watching_for_changes: bool,
116        unapproved_path_mode: UnapprovedPathMode,
117    ) -> Self {
118        Self::new_with_loaders(
119            sources,
120            Default::default(),
121            mode,
122            meta_check,
123            watching_for_changes,
124            unapproved_path_mode,
125        )
126    }
127
128    pub(crate) fn new_with_loaders(
129        sources: Arc<AssetSources>,
130        loaders: Arc<RwLock<AssetLoaders>>,
131        mode: AssetServerMode,
132        meta_check: AssetMetaCheck,
133        watching_for_changes: bool,
134        unapproved_path_mode: UnapprovedPathMode,
135    ) -> Self {
136        let (asset_event_sender, asset_event_receiver) = crossbeam_channel::unbounded();
137        let mut infos = AssetInfos::default();
138        infos.watching_for_changes = watching_for_changes;
139        Self {
140            data: Arc::new(AssetServerData {
141                sources,
142                mode,
143                meta_check,
144                asset_event_sender,
145                asset_event_receiver,
146                loaders,
147                infos: RwLock::new(infos),
148                unapproved_path_mode,
149            }),
150        }
151    }
152
153    pub(crate) fn read_infos(&self) -> RwLockReadGuard<'_, AssetInfos> {
154        self.data
155            .infos
156            .read()
157            .unwrap_or_else(PoisonError::into_inner)
158    }
159
160    pub(crate) fn write_infos(&self) -> RwLockWriteGuard<'_, AssetInfos> {
161        self.data
162            .infos
163            .write()
164            .unwrap_or_else(PoisonError::into_inner)
165    }
166
167    fn read_loaders(&self) -> RwLockReadGuard<'_, AssetLoaders> {
168        self.data
169            .loaders
170            .read()
171            .unwrap_or_else(PoisonError::into_inner)
172    }
173
174    fn write_loaders(&self) -> RwLockWriteGuard<'_, AssetLoaders> {
175        self.data
176            .loaders
177            .write()
178            .unwrap_or_else(PoisonError::into_inner)
179    }
180
181    /// Retrieves the [`AssetSource`] for the given `source`.
182    pub fn get_source<'a>(
183        &self,
184        source: impl Into<AssetSourceId<'a>>,
185    ) -> Result<&AssetSource, MissingAssetSourceError> {
186        self.data.sources.get(source.into())
187    }
188
189    /// Returns true if the [`AssetServer`] watches for changes.
190    pub fn watching_for_changes(&self) -> bool {
191        self.read_infos().watching_for_changes
192    }
193
194    /// Registers a new [`AssetLoader`]. [`AssetLoader`]s must be registered before they can be used.
195    pub fn register_loader<L: AssetLoader>(&self, loader: L) {
196        self.write_loaders().push(loader);
197    }
198
199    /// Registers a new [`Asset`] type. [`Asset`] types must be registered before assets of that type can be loaded.
200    pub fn register_asset<A: Asset>(&self, assets: &Assets<A>) {
201        self.register_handle_provider(assets.get_handle_provider());
202        fn sender<A: Asset>(world: &mut World, index: AssetIndex) {
203            world
204                .resource_mut::<Messages<AssetEvent<A>>>()
205                .write(AssetEvent::LoadedWithDependencies { id: index.into() });
206        }
207        fn failed_sender<A: Asset>(
208            world: &mut World,
209            index: AssetIndex,
210            path: AssetPath<'static>,
211            error: AssetLoadError,
212        ) {
213            world
214                .resource_mut::<Messages<AssetLoadFailedEvent<A>>>()
215                .write(AssetLoadFailedEvent {
216                    id: index.into(),
217                    path,
218                    error,
219                });
220        }
221
222        let mut infos = self.write_infos();
223
224        infos
225            .dependency_loaded_event_sender
226            .insert(TypeId::of::<A>(), sender::<A>);
227
228        infos
229            .dependency_failed_event_sender
230            .insert(TypeId::of::<A>(), failed_sender::<A>);
231    }
232
233    pub(crate) fn register_handle_provider(&self, handle_provider: AssetHandleProvider) {
234        self.write_infos()
235            .handle_providers
236            .insert(handle_provider.type_id, handle_provider);
237    }
238
239    /// Returns the registered [`AssetLoader`] associated with the given extension, if it exists.
240    pub async fn get_asset_loader_with_extension(
241        &self,
242        extension: &str,
243    ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
244        let error = || MissingAssetLoaderForExtensionError {
245            extensions: vec![extension.to_string()],
246        };
247
248        let loader = self
249            .read_loaders()
250            .get_by_extension(extension)
251            .ok_or_else(error)?;
252        loader.get().await.map_err(|_| error())
253    }
254
255    /// Returns the registered [`AssetLoader`] associated with the given type name, if it exists.
256    pub async fn get_asset_loader_with_type_name(
257        &self,
258        type_name: &str,
259    ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeNameError> {
260        let error = || MissingAssetLoaderForTypeNameError {
261            type_name: type_name.to_string(),
262        };
263
264        let loader = self
265            .read_loaders()
266            .get_by_name(type_name)
267            .ok_or_else(error)?;
268        loader.get().await.map_err(|_| error())
269    }
270
271    /// Retrieves the default [`AssetLoader`] for the given path, if one can be found.
272    pub async fn get_path_asset_loader<'a>(
273        &self,
274        path: impl Into<AssetPath<'a>>,
275    ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
276        let path = path.into();
277
278        let error = || {
279            let Some(full_extension) = path.get_full_extension() else {
280                return MissingAssetLoaderForExtensionError {
281                    extensions: Vec::new(),
282                };
283            };
284
285            let mut extensions = vec![full_extension.clone()];
286            extensions.extend(
287                AssetPath::iter_secondary_extensions(&full_extension).map(ToString::to_string),
288            );
289
290            MissingAssetLoaderForExtensionError { extensions }
291        };
292
293        let loader = self.read_loaders().get_by_path(&path).ok_or_else(error)?;
294        loader.get().await.map_err(|_| error())
295    }
296
297    /// Retrieves the default [`AssetLoader`] for the given [`Asset`] [`TypeId`], if one can be found.
298    pub async fn get_asset_loader_with_asset_type_id(
299        &self,
300        type_id: TypeId,
301    ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
302        let error = || MissingAssetLoaderForTypeIdError { type_id };
303
304        let loader = self.read_loaders().get_by_type(type_id).ok_or_else(error)?;
305        loader.get().await.map_err(|_| error())
306    }
307
308    /// Retrieves the default [`AssetLoader`] for the given [`Asset`] type, if one can be found.
309    pub async fn get_asset_loader_with_asset_type<A: Asset>(
310        &self,
311    ) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForTypeIdError> {
312        self.get_asset_loader_with_asset_type_id(TypeId::of::<A>())
313            .await
314    }
315
316    /// Begins loading an [`Asset`] of type `A` stored at `path`. This will not block on the asset load. Instead,
317    /// it returns a "strong" [`Handle`]. When the [`Asset`] is loaded (and enters [`LoadState::Loaded`]), it will be added to the
318    /// associated [`Assets`] resource.
319    ///
320    /// Note that if the asset at this path is already loaded, this function will return the existing handle,
321    /// and will not waste work spawning a new load task.
322    ///
323    /// In case the file path contains a hashtag (`#`), the `path` must be specified using [`Path`]
324    /// or [`AssetPath`] because otherwise the hashtag would be interpreted as separator between
325    /// the file path and the label. For example:
326    ///
327    /// ```no_run
328    /// # use bevy_asset::{AssetServer, Handle, LoadedUntypedAsset};
329    /// # use bevy_ecs::prelude::Res;
330    /// # use std::path::Path;
331    /// // `#path` is a label.
332    /// # fn setup(asset_server: Res<AssetServer>) {
333    /// # let handle: Handle<LoadedUntypedAsset> =
334    /// asset_server.load("some/file#path");
335    ///
336    /// // `#path` is part of the file name.
337    /// # let handle: Handle<LoadedUntypedAsset> =
338    /// asset_server.load(Path::new("some/file#path"));
339    /// # }
340    /// ```
341    ///
342    /// Furthermore, if you need to load a file with a hashtag in its name _and_ a label, you can
343    /// manually construct an [`AssetPath`].
344    ///
345    /// ```no_run
346    /// # use bevy_asset::{AssetPath, AssetServer, Handle, LoadedUntypedAsset};
347    /// # use bevy_ecs::prelude::Res;
348    /// # use std::path::Path;
349    /// # fn setup(asset_server: Res<AssetServer>) {
350    /// # let handle: Handle<LoadedUntypedAsset> =
351    /// asset_server.load(AssetPath::from_path(Path::new("some/file#path")).with_label("subasset"));
352    /// # }
353    /// ```
354    ///
355    /// You can check the asset's load state by reading [`AssetEvent`] events, calling [`AssetServer::load_state`], or checking
356    /// the [`Assets`] storage to see if the [`Asset`] exists yet.
357    ///
358    /// The asset load will fail and an error will be printed to the logs if the asset stored at `path` is not of type `A`.
359    #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
360    pub fn load<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
361        self.load_with_meta_transform(path, None, (), false)
362    }
363
364    /// Same as [`load`](AssetServer::load), but you can load assets from unapproved paths
365    /// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode)
366    /// is [`Deny`](UnapprovedPathMode::Deny).
367    ///
368    /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`]
369    pub fn load_override<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
370        self.load_with_meta_transform(path, None, (), true)
371    }
372
373    /// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item.
374    /// The guard item is dropped when either the asset is loaded or loading has failed.
375    ///
376    /// This function returns a "strong" [`Handle`]. When the [`Asset`] is loaded (and enters [`LoadState::Loaded`]), it will be added to the
377    /// associated [`Assets`] resource.
378    ///
379    /// The guard item should notify the caller in its [`Drop`] implementation. See example `multi_asset_sync`.
380    /// Synchronously this can be a [`Arc<AtomicU32>`] that decrements its counter, asynchronously this can be a `Barrier`.
381    /// This function only guarantees the asset referenced by the [`Handle`] is loaded. If your asset is separated into
382    /// multiple files, sub-assets referenced by the main asset might still be loading, depend on the implementation of the [`AssetLoader`].
383    ///
384    /// Additionally, you can check the asset's load state by reading [`AssetEvent`] events, calling [`AssetServer::load_state`], or checking
385    /// the [`Assets`] storage to see if the [`Asset`] exists yet.
386    ///
387    /// The asset load will fail and an error will be printed to the logs if the asset stored at `path` is not of type `A`.
388    #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
389    pub fn load_acquire<'a, A: Asset, G: Send + Sync + 'static>(
390        &self,
391        path: impl Into<AssetPath<'a>>,
392        guard: G,
393    ) -> Handle<A> {
394        self.load_with_meta_transform(path, None, guard, false)
395    }
396
397    /// Same as [`load`](AssetServer::load_acquire), but you can load assets from unapproved paths
398    /// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode)
399    /// is [`Deny`](UnapprovedPathMode::Deny).
400    ///
401    /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`]
402    pub fn load_acquire_override<'a, A: Asset, G: Send + Sync + 'static>(
403        &self,
404        path: impl Into<AssetPath<'a>>,
405        guard: G,
406    ) -> Handle<A> {
407        self.load_with_meta_transform(path, None, guard, true)
408    }
409
410    /// Begins loading an [`Asset`] of type `A` stored at `path`. The given `settings` function will override the asset's
411    /// [`AssetLoader`] settings. The type `S` _must_ match the configured [`AssetLoader::Settings`] or `settings` changes
412    /// will be ignored and an error will be printed to the log.
413    #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
414    pub fn load_with_settings<'a, A: Asset, S: Settings>(
415        &self,
416        path: impl Into<AssetPath<'a>>,
417        settings: impl Fn(&mut S) + Send + Sync + 'static,
418    ) -> Handle<A> {
419        self.load_with_meta_transform(
420            path,
421            Some(loader_settings_meta_transform(settings)),
422            (),
423            false,
424        )
425    }
426
427    /// Same as [`load`](AssetServer::load_with_settings), but you can load assets from unapproved paths
428    /// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode)
429    /// is [`Deny`](UnapprovedPathMode::Deny).
430    ///
431    /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`]
432    pub fn load_with_settings_override<'a, A: Asset, S: Settings>(
433        &self,
434        path: impl Into<AssetPath<'a>>,
435        settings: impl Fn(&mut S) + Send + Sync + 'static,
436    ) -> Handle<A> {
437        self.load_with_meta_transform(
438            path,
439            Some(loader_settings_meta_transform(settings)),
440            (),
441            true,
442        )
443    }
444
445    /// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item.
446    /// The guard item is dropped when either the asset is loaded or loading has failed.
447    ///
448    /// This function only guarantees the asset referenced by the [`Handle`] is loaded. If your asset is separated into
449    /// multiple files, sub-assets referenced by the main asset might still be loading, depend on the implementation of the [`AssetLoader`].
450    ///
451    /// The given `settings` function will override the asset's
452    /// [`AssetLoader`] settings. The type `S` _must_ match the configured [`AssetLoader::Settings`] or `settings` changes
453    /// will be ignored and an error will be printed to the log.
454    #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
455    pub fn load_acquire_with_settings<'a, A: Asset, S: Settings, G: Send + Sync + 'static>(
456        &self,
457        path: impl Into<AssetPath<'a>>,
458        settings: impl Fn(&mut S) + Send + Sync + 'static,
459        guard: G,
460    ) -> Handle<A> {
461        self.load_with_meta_transform(
462            path,
463            Some(loader_settings_meta_transform(settings)),
464            guard,
465            false,
466        )
467    }
468
469    /// Same as [`load`](AssetServer::load_acquire_with_settings), but you can load assets from unapproved paths
470    /// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode)
471    /// is [`Deny`](UnapprovedPathMode::Deny).
472    ///
473    /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`]
474    pub fn load_acquire_with_settings_override<
475        'a,
476        A: Asset,
477        S: Settings,
478        G: Send + Sync + 'static,
479    >(
480        &self,
481        path: impl Into<AssetPath<'a>>,
482        settings: impl Fn(&mut S) + Send + Sync + 'static,
483        guard: G,
484    ) -> Handle<A> {
485        self.load_with_meta_transform(
486            path,
487            Some(loader_settings_meta_transform(settings)),
488            guard,
489            true,
490        )
491    }
492
493    pub(crate) fn load_with_meta_transform<'a, A: Asset, G: Send + Sync + 'static>(
494        &self,
495        path: impl Into<AssetPath<'a>>,
496        meta_transform: Option<MetaTransform>,
497        guard: G,
498        override_unapproved: bool,
499    ) -> Handle<A> {
500        let path = path.into().into_owned();
501
502        if path.is_unapproved() {
503            match (&self.data.unapproved_path_mode, override_unapproved) {
504                (UnapprovedPathMode::Allow, _) | (UnapprovedPathMode::Deny, true) => {}
505                (UnapprovedPathMode::Deny, false) | (UnapprovedPathMode::Forbid, _) => {
506                    error!("Asset path {path} is unapproved. See UnapprovedPathMode for details.");
507                    return Handle::default();
508                }
509            }
510        }
511
512        let mut infos = self.write_infos();
513        let (handle, should_load) = infos.get_or_create_path_handle::<A>(
514            path.clone(),
515            HandleLoadingMode::Request,
516            meta_transform,
517        );
518
519        if should_load {
520            self.spawn_load_task(handle.clone().untyped(), path, infos, guard);
521        }
522
523        handle
524    }
525
526    pub(crate) fn load_erased_with_meta_transform<'a, G: Send + Sync + 'static>(
527        &self,
528        path: impl Into<AssetPath<'a>>,
529        type_id: TypeId,
530        meta_transform: Option<MetaTransform>,
531        guard: G,
532    ) -> UntypedHandle {
533        let path = path.into().into_owned();
534        let mut infos = self.write_infos();
535        let (handle, should_load) = infos.get_or_create_path_handle_erased(
536            path.clone(),
537            type_id,
538            None,
539            HandleLoadingMode::Request,
540            meta_transform,
541        );
542
543        if should_load {
544            self.spawn_load_task(handle.clone(), path, infos, guard);
545        }
546
547        handle
548    }
549
550    pub(crate) fn spawn_load_task<G: Send + Sync + 'static>(
551        &self,
552        handle: UntypedHandle,
553        path: AssetPath<'static>,
554        mut infos: RwLockWriteGuard<AssetInfos>,
555        guard: G,
556    ) {
557        infos.stats.started_load_tasks += 1;
558
559        // drop the lock on `AssetInfos` before spawning a task that may block on it in single-threaded
560        #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
561        drop(infos);
562
563        let owned_handle = handle.clone();
564        let server = self.clone();
565        let task = IoTaskPool::get().spawn(async move {
566            if let Err(err) = server
567                .load_internal(Some(owned_handle), path, false, None)
568                .await
569            {
570                error!("{}", err);
571            }
572            drop(guard);
573        });
574
575        #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
576        {
577            let mut infos = infos;
578            infos
579                .pending_tasks
580                .insert((&handle).try_into().unwrap(), task);
581        }
582
583        #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
584        task.detach();
585    }
586
587    /// Asynchronously load an asset that you do not know the type of statically. If you _do_ know the type of the asset,
588    /// you should use [`AssetServer::load`]. If you don't know the type of the asset, but you can't use an async method,
589    /// consider using [`AssetServer::load_untyped`].
590    #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
591    pub async fn load_untyped_async<'a>(
592        &self,
593        path: impl Into<AssetPath<'a>>,
594    ) -> Result<UntypedHandle, AssetLoadError> {
595        self.write_infos().stats.started_load_tasks += 1;
596
597        let path: AssetPath = path.into();
598        self.load_internal(None, path, false, None)
599            .await
600            .map(|h| h.expect("handle must be returned, since we didn't pass in an input handle"))
601    }
602
603    pub(crate) fn load_unknown_type_with_meta_transform<'a>(
604        &self,
605        path: impl Into<AssetPath<'a>>,
606        meta_transform: Option<MetaTransform>,
607    ) -> Handle<LoadedUntypedAsset> {
608        let path = path.into().into_owned();
609        let untyped_source = AssetSourceId::Name(match path.source() {
610            AssetSourceId::Default => CowArc::Static(UNTYPED_SOURCE_SUFFIX),
611            AssetSourceId::Name(source) => {
612                CowArc::Owned(format!("{source}--{UNTYPED_SOURCE_SUFFIX}").into())
613            }
614        });
615        let mut infos = self.write_infos();
616        let (handle, should_load) = infos.get_or_create_path_handle::<LoadedUntypedAsset>(
617            path.clone().with_source(untyped_source),
618            HandleLoadingMode::Request,
619            meta_transform,
620        );
621
622        if !should_load {
623            return handle;
624        }
625        let index = (&handle).try_into().unwrap();
626
627        infos.stats.started_load_tasks += 1;
628
629        // drop the lock on `AssetInfos` before spawning a task that may block on it in single-threaded
630        #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
631        drop(infos);
632
633        let server = self.clone();
634        let task = IoTaskPool::get().spawn(async move {
635            let path_clone = path.clone();
636            match server
637                .load_internal(None, path, false, None)
638                .await
639                .map(|h| {
640                    h.expect("handle must be returned, since we didn't pass in an input handle")
641                }) {
642                Ok(handle) => server.send_asset_event(InternalAssetEvent::Loaded {
643                    index,
644                    loaded_asset: LoadedAsset::new_with_dependencies(LoadedUntypedAsset { handle })
645                        .into(),
646                }),
647                Err(err) => {
648                    error!("{err}");
649                    server.send_asset_event(InternalAssetEvent::Failed {
650                        index,
651                        path: path_clone,
652                        error: err,
653                    });
654                }
655            };
656        });
657
658        #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
659        infos.pending_tasks.insert(index, task);
660
661        #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
662        task.detach();
663
664        handle
665    }
666
667    /// Load an asset without knowing its type. The method returns a handle to a [`LoadedUntypedAsset`].
668    ///
669    /// Once the [`LoadedUntypedAsset`] is loaded, an untyped handle for the requested path can be
670    /// retrieved from it.
671    ///
672    /// ```
673    /// use bevy_asset::{Assets, Handle, LoadedUntypedAsset};
674    /// use bevy_ecs::system::Res;
675    /// use bevy_ecs::resource::Resource;
676    ///
677    /// #[derive(Resource)]
678    /// struct LoadingUntypedHandle(Handle<LoadedUntypedAsset>);
679    ///
680    /// fn resolve_loaded_untyped_handle(loading_handle: Res<LoadingUntypedHandle>, loaded_untyped_assets: Res<Assets<LoadedUntypedAsset>>) {
681    ///     if let Some(loaded_untyped_asset) = loaded_untyped_assets.get(&loading_handle.0) {
682    ///         let handle = loaded_untyped_asset.handle.clone();
683    ///         // continue working with `handle` which points to the asset at the originally requested path
684    ///     }
685    /// }
686    /// ```
687    ///
688    /// This indirection enables a non blocking load of an untyped asset, since I/O is
689    /// required to figure out the asset type before a handle can be created.
690    #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
691    pub fn load_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedUntypedAsset> {
692        self.load_unknown_type_with_meta_transform(path, None)
693    }
694
695    /// Performs an async asset load.
696    ///
697    /// `input_handle` must only be [`Some`] if `should_load` was true when retrieving
698    /// `input_handle`. This is an optimization to avoid looking up `should_load` twice, but it
699    /// means you _must_ be sure a load is necessary when calling this function with [`Some`].
700    ///
701    /// Returns the handle of the asset if one was retrieved by this function. Otherwise, may return
702    /// [`None`].
703    async fn load_internal<'a>(
704        &self,
705        input_handle: Option<UntypedHandle>,
706        path: AssetPath<'a>,
707        force: bool,
708        meta_transform: Option<MetaTransform>,
709    ) -> Result<Option<UntypedHandle>, AssetLoadError> {
710        let input_handle_type_id = input_handle.as_ref().map(UntypedHandle::type_id);
711
712        let path = path.into_owned();
713        let path_clone = path.clone();
714        let (mut meta, loader, mut reader) = self
715            .get_meta_loader_and_reader(&path_clone, input_handle_type_id)
716            .await
717            .inspect_err(|e| {
718                // if there was an input handle, a "load" operation has already started, so we must produce a "failure" event, if
719                // we cannot find the meta and loader
720                if let Some(handle) = &input_handle {
721                    self.send_asset_event(InternalAssetEvent::Failed {
722                        index: handle.try_into().unwrap(),
723                        path: path.clone_owned(),
724                        error: e.clone(),
725                    });
726                }
727            })?;
728
729        if let Some(meta_transform) = input_handle.as_ref().and_then(|h| h.meta_transform()) {
730            (*meta_transform)(&mut *meta);
731        }
732
733        let asset_id: Option<ErasedAssetIndex>; // The asset ID of the asset we are trying to load.
734        let fetched_handle; // The handle if one was looked up/created.
735        let should_load; // Whether we need to load the asset.
736        if let Some(input_handle) = input_handle {
737            // This must have been created with `get_or_create_path_handle_internal` at some point,
738            // which only produces Strong variant handles, so this is safe.
739            asset_id = Some((&input_handle).try_into().unwrap());
740            // In this case, we intentionally drop the input handle so we can cancel loading the
741            // asset if the handle gets dropped (externally) before it finishes loading.
742            fetched_handle = None;
743            // The handle was passed in, so the "should_load" check was already done.
744            should_load = true;
745        } else {
746            // TODO: multiple asset loads for the same path can happen at the same time (rather than
747            // "early out-ing" in the "normal" case). This would be resolved by a universal asset
748            // id, as we would not need to resolve the asset type to generate the ID. See this
749            // issue: https://github.com/bevyengine/bevy/issues/10549
750
751            let mut infos = self.write_infos();
752            let result = infos.get_or_create_path_handle_internal(
753                path.clone(),
754                path.label().is_none().then(|| loader.asset_type_id()),
755                HandleLoadingMode::Request,
756                meta_transform,
757            );
758            match unwrap_with_context(result, Either::Left(loader.asset_type_name())) {
759                // We couldn't figure out the correct handle without its type ID (which can only
760                // happen if we are loading a subasset).
761                None => {
762                    // We don't know the expected type since the subasset may have a different type
763                    // than the "root" asset (which is the type the loader will load).
764                    asset_id = None;
765                    fetched_handle = None;
766                    // If we couldn't find an appropriate handle, then the asset certainly needs to
767                    // be loaded.
768                    should_load = true;
769                }
770                Some((handle, result_should_load)) => {
771                    // `get_or_create_path_handle_internal` always returns Strong variant, so this
772                    // is safe.
773                    asset_id = Some((&handle).try_into().unwrap());
774                    fetched_handle = Some(handle);
775                    should_load = result_should_load;
776                }
777            }
778        }
779        // Verify that the expected type matches the loader's type.
780        if let Some(asset_type_id) = asset_id.map(|id| id.type_id) {
781            // If we are loading a subasset, then the subasset's type almost certainly doesn't match
782            // the loader's type - and that's ok.
783            if path.label().is_none() && asset_type_id != loader.asset_type_id() {
784                error!(
785                    "Expected {:?}, got {:?}",
786                    asset_type_id,
787                    loader.asset_type_id()
788                );
789                return Err(AssetLoadError::RequestedHandleTypeMismatch {
790                    path: path.into_owned(),
791                    requested: asset_type_id,
792                    actual_asset_name: loader.asset_type_name(),
793                    loader_name: loader.type_path(),
794                });
795            }
796        }
797        // Bail out earlier if we don't need to load the asset.
798        if !should_load && !force {
799            return Ok(fetched_handle);
800        }
801
802        // We don't actually need to use _base_handle, but we do need to keep the handle alive.
803        // Dropping it would cancel the load of the base asset, which would make the load of this
804        // subasset never complete.
805        let (base_asset_id, _base_handle, base_path) = if path.label().is_some() {
806            let mut infos = self.write_infos();
807            let base_path = path.without_label().into_owned();
808            let base_handle = infos
809                .get_or_create_path_handle_erased(
810                    base_path.clone(),
811                    loader.asset_type_id(),
812                    Some(loader.asset_type_name()),
813                    HandleLoadingMode::Force,
814                    None,
815                )
816                .0;
817            (
818                // `get_or_create_path_handle_erased` always returns Strong variant, so this is
819                // safe.
820                (&base_handle).try_into().unwrap(),
821                Some(base_handle),
822                base_path,
823            )
824        } else {
825            (asset_id.unwrap(), None, path.clone())
826        };
827
828        match self
829            .load_with_settings_loader_and_reader(
830                &base_path,
831                meta.loader_settings().expect("meta is set to Load"),
832                &*loader,
833                &mut *reader,
834                true,
835                false,
836            )
837            .await
838        {
839            Ok(loaded_asset) => {
840                let final_handle = if let Some(label) = path.label_cow() {
841                    match loaded_asset.labeled_assets.get(&label) {
842                        Some(labeled_asset) => Some(labeled_asset.handle.clone()),
843                        None => {
844                            let mut all_labels: Vec<String> = loaded_asset
845                                .labeled_assets
846                                .keys()
847                                .map(|s| (**s).to_owned())
848                                .collect();
849                            all_labels.sort_unstable();
850                            return Err(AssetLoadError::MissingLabel {
851                                base_path,
852                                label: label.to_string(),
853                                all_labels,
854                            });
855                        }
856                    }
857                } else {
858                    fetched_handle
859                };
860
861                self.send_asset_event(InternalAssetEvent::Loaded {
862                    index: base_asset_id,
863                    loaded_asset,
864                });
865                Ok(final_handle)
866            }
867            Err(err) => {
868                self.send_asset_event(InternalAssetEvent::Failed {
869                    index: base_asset_id,
870                    error: err.clone(),
871                    path: path.into_owned(),
872                });
873                Err(err)
874            }
875        }
876    }
877
878    /// Kicks off a reload of the asset stored at the given path. This will only reload the asset if it currently loaded.
879    pub fn reload<'a>(&self, path: impl Into<AssetPath<'a>>) {
880        self.reload_internal(path, false);
881    }
882
883    fn reload_internal<'a>(&self, path: impl Into<AssetPath<'a>>, log: bool) {
884        let server = self.clone();
885        let path = path.into().into_owned();
886        IoTaskPool::get()
887            .spawn(async move {
888                let mut reloaded = false;
889
890                let requests = server
891                    .read_infos()
892                    .get_path_handles(&path)
893                    .map(|handle| server.load_internal(Some(handle), path.clone(), true, None))
894                    .collect::<Vec<_>>();
895
896                for result in requests {
897                    // Count each reload as a started load.
898                    server.write_infos().stats.started_load_tasks += 1;
899                    match result.await {
900                        Ok(_) => reloaded = true,
901                        Err(err) => error!("{}", err),
902                    }
903                }
904
905                if !reloaded && server.read_infos().should_reload(&path) {
906                    server.write_infos().stats.started_load_tasks += 1;
907                    match server.load_internal(None, path.clone(), true, None).await {
908                        Ok(_) => reloaded = true,
909                        Err(err) => error!("{}", err),
910                    }
911                }
912
913                if log && reloaded {
914                    info!("Reloaded {}", path);
915                }
916            })
917            .detach();
918    }
919
920    /// Queues a new asset to be tracked by the [`AssetServer`] and returns a [`Handle`] to it. This can be used to track
921    /// dependencies of assets created at runtime.
922    ///
923    /// After the asset has been fully loaded by the [`AssetServer`], it will show up in the relevant [`Assets`] storage.
924    #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
925    pub fn add<A: Asset>(&self, asset: A) -> Handle<A> {
926        self.load_asset(LoadedAsset::new_with_dependencies(asset))
927    }
928
929    pub(crate) fn load_asset<A: Asset>(&self, asset: impl Into<LoadedAsset<A>>) -> Handle<A> {
930        let loaded_asset: LoadedAsset<A> = asset.into();
931        let erased_loaded_asset: ErasedLoadedAsset = loaded_asset.into();
932        self.load_asset_untyped(None, erased_loaded_asset)
933            .typed_debug_checked()
934    }
935
936    #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
937    pub(crate) fn load_asset_untyped(
938        &self,
939        path: Option<AssetPath<'static>>,
940        asset: impl Into<ErasedLoadedAsset>,
941    ) -> UntypedHandle {
942        let loaded_asset = asset.into();
943        let handle = if let Some(path) = path {
944            let (handle, _) = self.write_infos().get_or_create_path_handle_erased(
945                path,
946                loaded_asset.asset_type_id(),
947                Some(loaded_asset.asset_type_name()),
948                HandleLoadingMode::NotLoading,
949                None,
950            );
951            handle
952        } else {
953            self.write_infos().create_loading_handle_untyped(
954                loaded_asset.asset_type_id(),
955                loaded_asset.asset_type_name(),
956            )
957        };
958        self.send_asset_event(InternalAssetEvent::Loaded {
959            // `get_or_create_path_handle_erased` always returns Strong variant, so this is safe.
960            index: (&handle).try_into().unwrap(),
961            loaded_asset,
962        });
963        handle
964    }
965
966    /// Queues a new asset to be tracked by the [`AssetServer`] and returns a [`Handle`] to it. This can be used to track
967    /// dependencies of assets created at runtime.
968    ///
969    /// After the asset has been fully loaded, it will show up in the relevant [`Assets`] storage.
970    #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
971    pub fn add_async<A: Asset, E: core::error::Error + Send + Sync + 'static>(
972        &self,
973        future: impl Future<Output = Result<A, E>> + Send + 'static,
974    ) -> Handle<A> {
975        let mut infos = self.write_infos();
976        let handle =
977            infos.create_loading_handle_untyped(TypeId::of::<A>(), core::any::type_name::<A>());
978
979        // drop the lock on `AssetInfos` before spawning a task that may block on it in single-threaded
980        #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
981        drop(infos);
982
983        // `create_loading_handle_untyped` always returns a Strong variant, so this is safe.
984        let index = (&handle).try_into().unwrap();
985
986        let event_sender = self.data.asset_event_sender.clone();
987
988        let task = IoTaskPool::get().spawn(async move {
989            match future.await {
990                Ok(asset) => {
991                    let loaded_asset = LoadedAsset::new_with_dependencies(asset).into();
992                    event_sender
993                        .send(InternalAssetEvent::Loaded {
994                            index,
995                            loaded_asset,
996                        })
997                        .unwrap();
998                }
999                Err(error) => {
1000                    let error = AddAsyncError {
1001                        error: Arc::new(error),
1002                    };
1003                    error!("{error}");
1004                    event_sender
1005                        .send(InternalAssetEvent::Failed {
1006                            index,
1007                            path: Default::default(),
1008                            error: AssetLoadError::AddAsyncError(error),
1009                        })
1010                        .unwrap();
1011                }
1012            }
1013        });
1014
1015        #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
1016        infos.pending_tasks.insert(index, task);
1017
1018        #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
1019        task.detach();
1020
1021        handle.typed_debug_checked()
1022    }
1023
1024    /// Loads all assets from the specified folder recursively. The [`LoadedFolder`] asset (when it loads) will
1025    /// contain handles to all assets in the folder. You can wait for all assets to load by checking the [`LoadedFolder`]'s
1026    /// [`RecursiveDependencyLoadState`].
1027    ///
1028    /// Loading the same folder multiple times will return the same handle. If the `file_watcher`
1029    /// feature is enabled, [`LoadedFolder`] handles will reload when a file in the folder is
1030    /// removed, added or moved. This includes files in subdirectories and moving, adding,
1031    /// or removing complete subdirectories.
1032    #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
1033    pub fn load_folder<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedFolder> {
1034        let path = path.into().into_owned();
1035        let (handle, should_load) = self
1036            .write_infos()
1037            .get_or_create_path_handle::<LoadedFolder>(
1038                path.clone(),
1039                HandleLoadingMode::Request,
1040                None,
1041            );
1042        if !should_load {
1043            return handle;
1044        }
1045        // `get_or_create_path_handle` always returns a Strong variant, so this is safe.
1046        let index = (&handle).try_into().unwrap();
1047        self.load_folder_internal(index, path);
1048
1049        handle
1050    }
1051
1052    pub(crate) fn load_folder_internal(&self, index: ErasedAssetIndex, path: AssetPath) {
1053        async fn load_folder<'a>(
1054            source: AssetSourceId<'static>,
1055            path: &'a Path,
1056            reader: &'a dyn ErasedAssetReader,
1057            server: &'a AssetServer,
1058            handles: &'a mut Vec<UntypedHandle>,
1059        ) -> Result<(), AssetLoadError> {
1060            let is_dir = reader.is_directory(path).await?;
1061            if is_dir {
1062                let mut path_stream = reader.read_directory(path.as_ref()).await?;
1063                while let Some(child_path) = path_stream.next().await {
1064                    if reader.is_directory(&child_path).await? {
1065                        Box::pin(load_folder(
1066                            source.clone(),
1067                            &child_path,
1068                            reader,
1069                            server,
1070                            handles,
1071                        ))
1072                        .await?;
1073                    } else {
1074                        let path = child_path.to_str().expect("Path should be a valid string.");
1075                        let asset_path = AssetPath::parse(path).with_source(source.clone());
1076                        match server.load_untyped_async(asset_path).await {
1077                            Ok(handle) => handles.push(handle),
1078                            // skip assets that cannot be loaded
1079                            Err(
1080                                AssetLoadError::MissingAssetLoaderForTypeName(_)
1081                                | AssetLoadError::MissingAssetLoaderForExtension(_),
1082                            ) => {}
1083                            Err(err) => return Err(err),
1084                        }
1085                    }
1086                }
1087            }
1088            Ok(())
1089        }
1090
1091        self.write_infos().stats.started_load_tasks += 1;
1092
1093        let path = path.into_owned();
1094        let server = self.clone();
1095        IoTaskPool::get()
1096            .spawn(async move {
1097                let Ok(source) = server.get_source(path.source()) else {
1098                    error!(
1099                        "Failed to load {path}. AssetSource {} does not exist",
1100                        path.source()
1101                    );
1102                    return;
1103                };
1104
1105                let asset_reader = match server.data.mode {
1106                    AssetServerMode::Unprocessed => source.reader(),
1107                    AssetServerMode::Processed => match source.processed_reader() {
1108                        Ok(reader) => reader,
1109                        Err(_) => {
1110                            error!(
1111                                "Failed to load {path}. AssetSource {} does not have a processed AssetReader",
1112                                path.source()
1113                            );
1114                            return;
1115                        }
1116                    },
1117                };
1118
1119                let mut handles = Vec::new();
1120                match load_folder(source.id(), path.path(), asset_reader, &server, &mut handles).await {
1121                    Ok(_) => server.send_asset_event(InternalAssetEvent::Loaded {
1122                        index,
1123                        loaded_asset: LoadedAsset::new_with_dependencies(
1124                            LoadedFolder { handles },
1125                        )
1126                        .into(),
1127                    }),
1128                    Err(err) => {
1129                        error!("Failed to load folder. {err}");
1130                        server.send_asset_event(InternalAssetEvent::Failed { index, error: err, path });
1131                    },
1132                }
1133            })
1134            .detach();
1135    }
1136
1137    fn send_asset_event(&self, event: InternalAssetEvent) {
1138        self.data.asset_event_sender.send(event).unwrap();
1139    }
1140
1141    /// Retrieves all loads states for the given asset id.
1142    pub fn get_load_states(
1143        &self,
1144        id: impl Into<UntypedAssetId>,
1145    ) -> Option<(LoadState, DependencyLoadState, RecursiveDependencyLoadState)> {
1146        let Ok(index) = id.into().try_into() else {
1147            // Always say we don't have Uuid assets.
1148            return None;
1149        };
1150        self.read_infos().get(index).map(|i| {
1151            (
1152                i.load_state.clone(),
1153                i.dep_load_state.clone(),
1154                i.rec_dep_load_state.clone(),
1155            )
1156        })
1157    }
1158
1159    /// Retrieves the main [`LoadState`] of a given asset `id`.
1160    ///
1161    /// Note that this is "just" the root asset load state. To get the load state of
1162    /// its dependencies or recursive dependencies, see [`AssetServer::get_dependency_load_state`]
1163    /// and [`AssetServer::get_recursive_dependency_load_state`] respectively.
1164    pub fn get_load_state(&self, id: impl Into<UntypedAssetId>) -> Option<LoadState> {
1165        let Ok(index) = id.into().try_into() else {
1166            // Always say we don't have Uuid assets.
1167            return None;
1168        };
1169        self.read_infos().get(index).map(|i| i.load_state.clone())
1170    }
1171
1172    /// Retrieves the [`DependencyLoadState`] of a given asset `id`'s dependencies.
1173    ///
1174    /// Note that this is only the load state of direct dependencies of the root asset. To get
1175    /// the load state of the root asset itself or its recursive dependencies, see
1176    /// [`AssetServer::get_load_state`] and [`AssetServer::get_recursive_dependency_load_state`] respectively.
1177    pub fn get_dependency_load_state(
1178        &self,
1179        id: impl Into<UntypedAssetId>,
1180    ) -> Option<DependencyLoadState> {
1181        let Ok(index) = id.into().try_into() else {
1182            // Always say we don't have Uuid assets.
1183            return None;
1184        };
1185        self.read_infos()
1186            .get(index)
1187            .map(|i| i.dep_load_state.clone())
1188    }
1189
1190    /// Retrieves the main [`RecursiveDependencyLoadState`] of a given asset `id`'s recursive dependencies.
1191    ///
1192    /// Note that this is only the load state of recursive dependencies of the root asset. To get
1193    /// the load state of the root asset itself or its direct dependencies only, see
1194    /// [`AssetServer::get_load_state`] and [`AssetServer::get_dependency_load_state`] respectively.
1195    pub fn get_recursive_dependency_load_state(
1196        &self,
1197        id: impl Into<UntypedAssetId>,
1198    ) -> Option<RecursiveDependencyLoadState> {
1199        let Ok(index) = id.into().try_into() else {
1200            // Always say we don't have Uuid assets.
1201            return None;
1202        };
1203        self.read_infos()
1204            .get(index)
1205            .map(|i| i.rec_dep_load_state.clone())
1206    }
1207
1208    /// Retrieves the main [`LoadState`] of a given asset `id`.
1209    ///
1210    /// This is the same as [`AssetServer::get_load_state`] except the result is unwrapped. If
1211    /// the result is None, [`LoadState::NotLoaded`] is returned.
1212    pub fn load_state(&self, id: impl Into<UntypedAssetId>) -> LoadState {
1213        self.get_load_state(id).unwrap_or(LoadState::NotLoaded)
1214    }
1215
1216    /// Retrieves the [`DependencyLoadState`] of a given asset `id`.
1217    ///
1218    /// This is the same as [`AssetServer::get_dependency_load_state`] except the result is unwrapped. If
1219    /// the result is None, [`DependencyLoadState::NotLoaded`] is returned.
1220    pub fn dependency_load_state(&self, id: impl Into<UntypedAssetId>) -> DependencyLoadState {
1221        self.get_dependency_load_state(id)
1222            .unwrap_or(DependencyLoadState::NotLoaded)
1223    }
1224
1225    /// Retrieves the  [`RecursiveDependencyLoadState`] of a given asset `id`.
1226    ///
1227    /// This is the same as [`AssetServer::get_recursive_dependency_load_state`] except the result is unwrapped. If
1228    /// the result is None, [`RecursiveDependencyLoadState::NotLoaded`] is returned.
1229    pub fn recursive_dependency_load_state(
1230        &self,
1231        id: impl Into<UntypedAssetId>,
1232    ) -> RecursiveDependencyLoadState {
1233        self.get_recursive_dependency_load_state(id)
1234            .unwrap_or(RecursiveDependencyLoadState::NotLoaded)
1235    }
1236
1237    /// Convenience method that returns true if the asset has been loaded.
1238    pub fn is_loaded(&self, id: impl Into<UntypedAssetId>) -> bool {
1239        matches!(self.load_state(id), LoadState::Loaded)
1240    }
1241
1242    /// Convenience method that returns true if the asset and all of its direct dependencies have been loaded.
1243    pub fn is_loaded_with_direct_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
1244        matches!(
1245            self.get_load_states(id),
1246            Some((LoadState::Loaded, DependencyLoadState::Loaded, _))
1247        )
1248    }
1249
1250    /// Convenience method that returns true if the asset, all of its dependencies, and all of its recursive
1251    /// dependencies have been loaded.
1252    pub fn is_loaded_with_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
1253        matches!(
1254            self.get_load_states(id),
1255            Some((
1256                LoadState::Loaded,
1257                DependencyLoadState::Loaded,
1258                RecursiveDependencyLoadState::Loaded
1259            ))
1260        )
1261    }
1262
1263    /// Returns an active handle for the given path, if the asset at the given path has already started loading,
1264    /// or is still "alive".
1265    pub fn get_handle<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Option<Handle<A>> {
1266        self.get_path_and_type_id_handle(&path.into(), TypeId::of::<A>())
1267            .map(UntypedHandle::typed_debug_checked)
1268    }
1269
1270    /// Get a `Handle` from an `AssetId`.
1271    ///
1272    /// This only returns `Some` if `id` is derived from a `Handle` that was
1273    /// loaded through an `AssetServer`, otherwise it returns `None`.
1274    ///
1275    /// Consider using [`Assets::get_strong_handle`] in the case the `Handle`
1276    /// comes from [`Assets::add`].
1277    pub fn get_id_handle<A: Asset>(&self, id: AssetId<A>) -> Option<Handle<A>> {
1278        self.get_id_handle_untyped(id.untyped())
1279            .map(UntypedHandle::typed)
1280    }
1281
1282    /// Get an `UntypedHandle` from an `UntypedAssetId`.
1283    /// See [`AssetServer::get_id_handle`] for details.
1284    pub fn get_id_handle_untyped(&self, id: UntypedAssetId) -> Option<UntypedHandle> {
1285        let Ok(index) = id.try_into() else {
1286            // Always say we don't have Uuid assets.
1287            return None;
1288        };
1289        self.read_infos().get_index_handle(index)
1290    }
1291
1292    /// Returns `true` if the given `id` corresponds to an asset that is managed by this [`AssetServer`].
1293    /// Otherwise, returns `false`.
1294    pub fn is_managed(&self, id: impl Into<UntypedAssetId>) -> bool {
1295        let Ok(index) = id.into().try_into() else {
1296            // Always say we don't have Uuid assets.
1297            return false;
1298        };
1299        self.read_infos().contains_key(index)
1300    }
1301
1302    /// Returns an active untyped asset id for the given path, if the asset at the given path has already started loading,
1303    /// or is still "alive".
1304    /// Returns the first ID in the event of multiple assets being registered against a single path.
1305    ///
1306    /// # See also
1307    /// [`get_path_ids`][Self::get_path_ids] for all handles.
1308    pub fn get_path_id<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedAssetId> {
1309        let infos = self.read_infos();
1310        let path = path.into();
1311        let mut ids = infos.get_path_indices(&path);
1312        ids.next().map(Into::into)
1313    }
1314
1315    /// Returns all active untyped asset IDs for the given path, if the assets at the given path have already started loading,
1316    /// or are still "alive".
1317    /// Multiple IDs will be returned in the event that a single path is used by multiple [`AssetLoader`]'s.
1318    pub fn get_path_ids<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedAssetId> {
1319        let path = path.into();
1320        self.read_infos()
1321            .get_path_indices(&path)
1322            .map(Into::into)
1323            .collect()
1324    }
1325
1326    /// Returns an active untyped handle for the given path, if the asset at the given path has already started loading,
1327    /// or is still "alive".
1328    /// Returns the first handle in the event of multiple assets being registered against a single path.
1329    ///
1330    /// # See also
1331    /// [`get_handles_untyped`][Self::get_handles_untyped] for all handles.
1332    pub fn get_handle_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedHandle> {
1333        let path = path.into();
1334        self.read_infos().get_path_handles(&path).next()
1335    }
1336
1337    /// Returns all active untyped handles for the given path, if the assets at the given path have already started loading,
1338    /// or are still "alive".
1339    /// Multiple handles will be returned in the event that a single path is used by multiple [`AssetLoader`]'s.
1340    pub fn get_handles_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Vec<UntypedHandle> {
1341        let path = path.into();
1342        self.read_infos().get_path_handles(&path).collect()
1343    }
1344
1345    /// Returns an active untyped handle for the given path and [`TypeId`], if the asset at the given path has already started loading,
1346    /// or is still "alive".
1347    pub fn get_path_and_type_id_handle(
1348        &self,
1349        path: &AssetPath,
1350        type_id: TypeId,
1351    ) -> Option<UntypedHandle> {
1352        let path = path.into();
1353        self.read_infos()
1354            .get_path_and_type_id_handle(&path, type_id)
1355    }
1356
1357    /// Returns the path for the given `id`, if it has one.
1358    pub fn get_path(&self, id: impl Into<UntypedAssetId>) -> Option<AssetPath<'_>> {
1359        let Ok(index) = id.into().try_into() else {
1360            // Always say we don't have Uuid assets.
1361            return None;
1362        };
1363        let infos = self.read_infos();
1364        let info = infos.get(index)?;
1365        Some(info.path.as_ref()?.clone())
1366    }
1367
1368    /// Returns the [`AssetServerMode`] this server is currently in.
1369    pub fn mode(&self) -> AssetServerMode {
1370        self.data.mode
1371    }
1372
1373    /// Pre-register a loader that will later be added.
1374    ///
1375    /// Assets loaded with matching extensions will be blocked until the
1376    /// real loader is added.
1377    pub fn preregister_loader<L: AssetLoader>(&self, extensions: &[&str]) {
1378        self.write_loaders().reserve::<L>(extensions);
1379    }
1380
1381    /// Retrieve a handle for the given path. This will create a handle (and [`AssetInfo`]) if it does not exist
1382    pub(crate) fn get_or_create_path_handle<'a, A: Asset>(
1383        &self,
1384        path: impl Into<AssetPath<'a>>,
1385        meta_transform: Option<MetaTransform>,
1386    ) -> Handle<A> {
1387        self.write_infos()
1388            .get_or_create_path_handle::<A>(
1389                path.into().into_owned(),
1390                HandleLoadingMode::NotLoading,
1391                meta_transform,
1392            )
1393            .0
1394    }
1395
1396    /// Retrieve a handle for the given path, where the asset type ID and name
1397    /// are not known statically.
1398    ///
1399    /// This will create a handle (and [`AssetInfo`]) if it does not exist.
1400    pub(crate) fn get_or_create_path_handle_erased<'a>(
1401        &self,
1402        path: impl Into<AssetPath<'a>>,
1403        type_id: TypeId,
1404        meta_transform: Option<MetaTransform>,
1405    ) -> UntypedHandle {
1406        self.write_infos()
1407            .get_or_create_path_handle_erased(
1408                path.into().into_owned(),
1409                type_id,
1410                None,
1411                HandleLoadingMode::NotLoading,
1412                meta_transform,
1413            )
1414            .0
1415    }
1416
1417    pub(crate) async fn get_meta_loader_and_reader<'a>(
1418        &'a self,
1419        asset_path: &'a AssetPath<'_>,
1420        asset_type_id: Option<TypeId>,
1421    ) -> Result<
1422        (
1423            Box<dyn AssetMetaDyn>,
1424            Arc<dyn ErasedAssetLoader>,
1425            Box<dyn Reader + 'a>,
1426        ),
1427        AssetLoadError,
1428    > {
1429        let source = self.get_source(asset_path.source())?;
1430        let asset_reader = match self.data.mode {
1431            AssetServerMode::Unprocessed => source.reader(),
1432            AssetServerMode::Processed => source.processed_reader()?,
1433        };
1434        let read_meta = match &self.data.meta_check {
1435            AssetMetaCheck::Always => true,
1436            AssetMetaCheck::Paths(paths) => paths.contains(asset_path),
1437            AssetMetaCheck::Never => false,
1438        };
1439
1440        // Scope the meta reader up here. This allows the reader to be "transactional": for sources
1441        // that want to lock the asset before reading it (e.g., with a RwLock), this allows the meta
1442        // reader to take the RwLock, and since it overlaps with the asset reader, the asset reader
1443        // can "take over" the RwLock before the meta reader gets dropped.
1444        let mut meta_reader;
1445
1446        let (meta, loader) = if read_meta {
1447            match asset_reader.read_meta(asset_path.path()).await {
1448                Ok(new_meta_reader) => {
1449                    meta_reader = new_meta_reader;
1450                    let mut meta_bytes = vec![];
1451                    meta_reader
1452                        .read_to_end(&mut meta_bytes)
1453                        .await
1454                        .map_err(|err| AssetLoadError::AssetReaderError(err.into()))?;
1455                    // TODO: this isn't fully minimal yet. we only need the loader
1456                    let minimal: AssetMetaMinimal =
1457                        ron::de::from_bytes(&meta_bytes).map_err(|e| {
1458                            AssetLoadError::DeserializeMeta {
1459                                path: asset_path.clone_owned(),
1460                                error: DeserializeMetaError::DeserializeMinimal(e).into(),
1461                            }
1462                        })?;
1463                    let loader_name = match minimal.asset {
1464                        AssetActionMinimal::Load { loader } => loader,
1465                        AssetActionMinimal::Process { .. } => {
1466                            return Err(AssetLoadError::CannotLoadProcessedAsset {
1467                                path: asset_path.clone_owned(),
1468                            })
1469                        }
1470                        AssetActionMinimal::Ignore => {
1471                            return Err(AssetLoadError::CannotLoadIgnoredAsset {
1472                                path: asset_path.clone_owned(),
1473                            })
1474                        }
1475                    };
1476                    let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
1477                    let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
1478                        AssetLoadError::DeserializeMeta {
1479                            path: asset_path.clone_owned(),
1480                            error: e.into(),
1481                        }
1482                    })?;
1483
1484                    (meta, loader)
1485                }
1486                Err(AssetReaderError::NotFound(_)) => {
1487                    // TODO: Handle error transformation
1488                    let loader = {
1489                        self.read_loaders()
1490                            .find(None, asset_type_id, None, Some(asset_path))
1491                    };
1492
1493                    let error = || AssetLoadError::MissingAssetLoader {
1494                        loader_name: None,
1495                        asset_type_id,
1496                        extension: None,
1497                        asset_path: Some(asset_path.to_string()),
1498                    };
1499
1500                    let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1501
1502                    let meta = loader.default_meta();
1503                    (meta, loader)
1504                }
1505                Err(err) => return Err(err.into()),
1506            }
1507        } else {
1508            let loader = {
1509                self.read_loaders()
1510                    .find(None, asset_type_id, None, Some(asset_path))
1511            };
1512
1513            let error = || AssetLoadError::MissingAssetLoader {
1514                loader_name: None,
1515                asset_type_id,
1516                extension: None,
1517                asset_path: Some(asset_path.to_string()),
1518            };
1519
1520            let loader = loader.ok_or_else(error)?.get().await.map_err(|_| error())?;
1521
1522            let meta = loader.default_meta();
1523            (meta, loader)
1524        };
1525        let required_features =
1526            loader.reader_required_features(meta.loader_settings().expect("meta specifies load"));
1527        let reader = asset_reader
1528            .read(asset_path.path(), required_features)
1529            .await?;
1530        Ok((meta, loader, reader))
1531    }
1532
1533    pub(crate) async fn load_with_settings_loader_and_reader(
1534        &self,
1535        asset_path: &AssetPath<'_>,
1536        settings: &dyn Settings,
1537        loader: &dyn ErasedAssetLoader,
1538        reader: &mut dyn Reader,
1539        load_dependencies: bool,
1540        populate_hashes: bool,
1541    ) -> Result<ErasedLoadedAsset, AssetLoadError> {
1542        // TODO: experiment with this
1543        let asset_path = asset_path.clone_owned();
1544        let load_context =
1545            LoadContext::new(self, asset_path.clone(), load_dependencies, populate_hashes);
1546        AssertUnwindSafe(loader.load(reader, settings, load_context))
1547            .catch_unwind()
1548            .await
1549            .map_err(|_| AssetLoadError::AssetLoaderPanic {
1550                path: asset_path.clone_owned(),
1551                loader_name: loader.type_path(),
1552            })?
1553            .map_err(|e| {
1554                AssetLoadError::AssetLoaderError(AssetLoaderError {
1555                    path: asset_path.clone_owned(),
1556                    loader_name: loader.type_path(),
1557                    error: e.into(),
1558                })
1559            })
1560    }
1561
1562    /// Returns a future that will suspend until the specified asset and its dependencies finish
1563    /// loading.
1564    ///
1565    /// # Errors
1566    ///
1567    /// This will return an error if the asset or any of its dependencies fail to load,
1568    /// or if the asset has not been queued up to be loaded.
1569    pub async fn wait_for_asset<A: Asset>(
1570        &self,
1571        // NOTE: We take a reference to a handle so we know it will outlive the future,
1572        // which ensures the handle won't be dropped while waiting for the asset.
1573        handle: &Handle<A>,
1574    ) -> Result<(), WaitForAssetError> {
1575        self.wait_for_asset_id(handle.id().untyped()).await
1576    }
1577
1578    /// Returns a future that will suspend until the specified asset and its dependencies finish
1579    /// loading.
1580    ///
1581    /// # Errors
1582    ///
1583    /// This will return an error if the asset or any of its dependencies fail to load,
1584    /// or if the asset has not been queued up to be loaded.
1585    pub async fn wait_for_asset_untyped(
1586        &self,
1587        // NOTE: We take a reference to a handle so we know it will outlive the future,
1588        // which ensures the handle won't be dropped while waiting for the asset.
1589        handle: &UntypedHandle,
1590    ) -> Result<(), WaitForAssetError> {
1591        self.wait_for_asset_id(handle.id()).await
1592    }
1593
1594    /// Returns a future that will suspend until the specified asset and its dependencies finish
1595    /// loading.
1596    ///
1597    /// Note that since an asset ID does not count as a reference to the asset,
1598    /// the future returned from this method will *not* keep the asset alive.
1599    /// This may lead to the asset unexpectedly being dropped while you are waiting for it to
1600    /// finish loading.
1601    ///
1602    /// When calling this method, make sure a strong handle is stored elsewhere to prevent the
1603    /// asset from being dropped.
1604    /// If you have access to an asset's strong [`Handle`], you should prefer to call
1605    /// [`AssetServer::wait_for_asset`]
1606    /// or [`wait_for_asset_untyped`](Self::wait_for_asset_untyped) to ensure the asset finishes
1607    /// loading.
1608    ///
1609    /// # Errors
1610    ///
1611    /// This will return an error if the asset or any of its dependencies fail to load,
1612    /// or if the asset has not been queued up to be loaded.
1613    pub async fn wait_for_asset_id(
1614        &self,
1615        id: impl Into<UntypedAssetId>,
1616    ) -> Result<(), WaitForAssetError> {
1617        let Ok(index) = id.into().try_into() else {
1618            // Always say we aren't loading Uuid assets.
1619            return Err(WaitForAssetError::NotLoaded);
1620        };
1621        core::future::poll_fn(move |cx| self.wait_for_asset_id_poll_fn(cx, index)).await
1622    }
1623
1624    /// Used by [`wait_for_asset_id`](AssetServer::wait_for_asset_id) in [`poll_fn`](core::future::poll_fn).
1625    fn wait_for_asset_id_poll_fn(
1626        &self,
1627        cx: &mut core::task::Context<'_>,
1628        index: ErasedAssetIndex,
1629    ) -> Poll<Result<(), WaitForAssetError>> {
1630        let infos = self.read_infos();
1631
1632        let Some(info) = infos.get(index) else {
1633            return Poll::Ready(Err(WaitForAssetError::NotLoaded));
1634        };
1635
1636        match (&info.load_state, &info.rec_dep_load_state) {
1637            (LoadState::Loaded, RecursiveDependencyLoadState::Loaded) => Poll::Ready(Ok(())),
1638            // Return an error immediately if the asset is not in the process of loading
1639            (LoadState::NotLoaded, _) => Poll::Ready(Err(WaitForAssetError::NotLoaded)),
1640            // If the asset is loading, leave our waker behind
1641            (LoadState::Loading, _)
1642            | (_, RecursiveDependencyLoadState::Loading)
1643            | (LoadState::Loaded, RecursiveDependencyLoadState::NotLoaded) => {
1644                // Check if our waker is already there
1645                let has_waker = info
1646                    .waiting_tasks
1647                    .iter()
1648                    .any(|waker| waker.will_wake(cx.waker()));
1649
1650                if has_waker {
1651                    return Poll::Pending;
1652                }
1653
1654                let mut infos = {
1655                    // Must drop read-only guard to acquire write guard
1656                    drop(infos);
1657                    self.write_infos()
1658                };
1659
1660                let Some(info) = infos.get_mut(index) else {
1661                    return Poll::Ready(Err(WaitForAssetError::NotLoaded));
1662                };
1663
1664                // If the load state changed while reacquiring the lock, immediately
1665                // reawaken the task
1666                let is_loading = matches!(
1667                    (&info.load_state, &info.rec_dep_load_state),
1668                    (LoadState::Loading, _)
1669                        | (_, RecursiveDependencyLoadState::Loading)
1670                        | (LoadState::Loaded, RecursiveDependencyLoadState::NotLoaded)
1671                );
1672
1673                if !is_loading {
1674                    cx.waker().wake_by_ref();
1675                } else {
1676                    // Leave our waker behind
1677                    info.waiting_tasks.push(cx.waker().clone());
1678                }
1679
1680                Poll::Pending
1681            }
1682            (LoadState::Failed(error), _) => {
1683                Poll::Ready(Err(WaitForAssetError::Failed(error.clone())))
1684            }
1685            (_, RecursiveDependencyLoadState::Failed(error)) => {
1686                Poll::Ready(Err(WaitForAssetError::DependencyFailed(error.clone())))
1687            }
1688        }
1689    }
1690
1691    /// Writes the default loader meta file for the provided `path`.
1692    ///
1693    /// This function only generates meta files that simply load the path directly. To generate a
1694    /// meta file that will use the default asset processor for the path, see
1695    /// [`AssetProcessor::write_default_meta_file_for_path`].
1696    ///
1697    /// Note if there is already a meta file for `path`, this function returns
1698    /// `Err(WriteDefaultMetaError::MetaAlreadyExists)`.
1699    ///
1700    /// [`AssetProcessor::write_default_meta_file_for_path`]:  crate::AssetProcessor::write_default_meta_file_for_path
1701    pub async fn write_default_loader_meta_file_for_path(
1702        &self,
1703        path: impl Into<AssetPath<'_>>,
1704    ) -> Result<(), WriteDefaultMetaError> {
1705        let path = path.into();
1706        let loader = self.get_path_asset_loader(&path).await?;
1707
1708        let meta = loader.default_meta();
1709        let serialized_meta = meta.serialize();
1710
1711        let source = self.get_source(path.source())?;
1712
1713        let reader = source.reader();
1714        match reader.read_meta_bytes(path.path()).await {
1715            Ok(_) => return Err(WriteDefaultMetaError::MetaAlreadyExists),
1716            Err(AssetReaderError::UnsupportedFeature(feature)) => panic!("reading the meta file never requests a feature, but the following feature is unsupported: {feature}"),
1717            Err(AssetReaderError::NotFound(_)) => {
1718                // The meta file couldn't be found so just fall through.
1719            }
1720            Err(AssetReaderError::Io(err)) => {
1721                return Err(WriteDefaultMetaError::IoErrorFromExistingMetaCheck(err))
1722            }
1723            Err(AssetReaderError::HttpError(err)) => {
1724                return Err(WriteDefaultMetaError::HttpErrorFromExistingMetaCheck(err))
1725            }
1726        }
1727
1728        let writer = source.writer()?;
1729        writer
1730            .write_meta_bytes(path.path(), &serialized_meta)
1731            .await?;
1732
1733        Ok(())
1734    }
1735}
1736
1737/// A system that manages internal [`AssetServer`] events, such as finalizing asset loads.
1738pub fn handle_internal_asset_events(world: &mut World) {
1739    world.resource_scope(|world, server: Mut<AssetServer>| {
1740        let mut infos = server.write_infos();
1741        let var_name = vec![];
1742        let mut untyped_failures = var_name;
1743        for event in server.data.asset_event_receiver.try_iter() {
1744            match event {
1745                InternalAssetEvent::Loaded {
1746                    index,
1747                    loaded_asset,
1748                } => {
1749                    infos.process_asset_load(
1750                        index,
1751                        loaded_asset,
1752                        world,
1753                        &server.data.asset_event_sender,
1754                    );
1755                }
1756                InternalAssetEvent::LoadedWithDependencies { index } => {
1757                    let sender = infos
1758                        .dependency_loaded_event_sender
1759                        .get(&index.type_id)
1760                        .expect("Asset event sender should exist");
1761                    sender(world, index.index);
1762                    if let Some(info) = infos.get_mut(index) {
1763                        for waker in info.waiting_tasks.drain(..) {
1764                            waker.wake();
1765                        }
1766                    }
1767                }
1768                InternalAssetEvent::Failed { index, path, error } => {
1769                    infos.process_asset_fail(index, error.clone());
1770
1771                    // Send untyped failure event
1772                    untyped_failures.push(UntypedAssetLoadFailedEvent {
1773                        id: index.into(),
1774                        path: path.clone(),
1775                        error: error.clone(),
1776                    });
1777
1778                    // Send typed failure event
1779                    let sender = infos
1780                        .dependency_failed_event_sender
1781                        .get(&index.type_id)
1782                        .expect("Asset failed event sender should exist");
1783                    sender(world, index.index, path, error);
1784                }
1785            }
1786        }
1787
1788        if !untyped_failures.is_empty() {
1789            world.write_message_batch(untyped_failures);
1790        }
1791
1792        // The following code all deals with hot-reloading, which we can skip if the server isn't
1793        // watching for changes.
1794        if !infos.watching_for_changes {
1795            return;
1796        }
1797
1798        fn queue_ancestors(
1799            asset_path: &AssetPath,
1800            infos: &AssetInfos,
1801            paths_to_reload: &mut HashSet<AssetPath<'static>>,
1802        ) {
1803            if let Some(dependents) = infos.loader_dependents.get(asset_path) {
1804                for dependent in dependents {
1805                    paths_to_reload.insert(dependent.to_owned());
1806                    queue_ancestors(dependent, infos, paths_to_reload);
1807                }
1808            }
1809        }
1810
1811        let reload_parent_folders = |path: &PathBuf, source: &AssetSourceId<'static>| {
1812            for parent in path.ancestors().skip(1) {
1813                let parent_asset_path =
1814                    AssetPath::from(parent.to_path_buf()).with_source(source.clone());
1815                for folder_handle in infos.get_path_handles(&parent_asset_path) {
1816                    info!("Reloading folder {parent_asset_path} because the content has changed");
1817                    // `get_path_handles` only returns Strong variants, so this is safe.
1818                    let index = (&folder_handle).try_into().unwrap();
1819                    server.load_folder_internal(index, parent_asset_path.clone());
1820                }
1821            }
1822        };
1823
1824        let mut paths_to_reload = <HashSet<_>>::default();
1825        let mut reload_path = |path: PathBuf, source: &AssetSourceId<'static>| {
1826            let path = AssetPath::from(path).with_source(source);
1827            queue_ancestors(&path, &infos, &mut paths_to_reload);
1828            paths_to_reload.insert(path);
1829        };
1830
1831        let mut handle_event = |source: AssetSourceId<'static>, event: AssetSourceEvent| {
1832            match event {
1833                AssetSourceEvent::AddedAsset(path) => {
1834                    reload_parent_folders(&path, &source);
1835                    reload_path(path, &source);
1836                }
1837                // TODO: if the asset was processed and the processed file was changed, the first modified event
1838                // should be skipped?
1839                AssetSourceEvent::ModifiedAsset(path) | AssetSourceEvent::ModifiedMeta(path) => {
1840                    reload_path(path, &source);
1841                }
1842                AssetSourceEvent::RenamedFolder { old, new } => {
1843                    reload_parent_folders(&old, &source);
1844                    reload_parent_folders(&new, &source);
1845                }
1846                AssetSourceEvent::RemovedAsset(path)
1847                | AssetSourceEvent::RemovedFolder(path)
1848                | AssetSourceEvent::AddedFolder(path) => {
1849                    reload_parent_folders(&path, &source);
1850                }
1851                _ => {}
1852            }
1853        };
1854
1855        for source in server.data.sources.iter() {
1856            match server.data.mode {
1857                AssetServerMode::Unprocessed => {
1858                    if let Some(receiver) = source.event_receiver() {
1859                        while let Ok(event) = receiver.try_recv() {
1860                            handle_event(source.id(), event);
1861                        }
1862                    }
1863                }
1864                AssetServerMode::Processed => {
1865                    if let Some(receiver) = source.processed_event_receiver() {
1866                        while let Ok(event) = receiver.try_recv() {
1867                            handle_event(source.id(), event);
1868                        }
1869                    }
1870                }
1871            }
1872        }
1873
1874        // Drop the lock on `AssetInfos` before spawning a task that may block on it in
1875        // single-threaded.
1876        #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
1877        drop(infos);
1878
1879        for path in paths_to_reload {
1880            server.reload_internal(path, true);
1881        }
1882
1883        #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))]
1884        infos
1885            .pending_tasks
1886            .retain(|_, load_task| !load_task.is_finished());
1887    });
1888}
1889
1890/// A system publishing asset server statistics to [`bevy_diagnostic`].
1891pub fn publish_asset_server_diagnostics(
1892    asset_server: Res<AssetServer>,
1893    mut diagnostics: Diagnostics,
1894) {
1895    let infos = asset_server.read_infos();
1896    diagnostics.add_measurement(&AssetServer::STARTED_LOAD_COUNT, || {
1897        infos.stats.started_load_tasks as _
1898    });
1899}
1900
1901/// Internal events for asset load results
1902pub(crate) enum InternalAssetEvent {
1903    Loaded {
1904        index: ErasedAssetIndex,
1905        loaded_asset: ErasedLoadedAsset,
1906    },
1907    LoadedWithDependencies {
1908        index: ErasedAssetIndex,
1909    },
1910    Failed {
1911        index: ErasedAssetIndex,
1912        path: AssetPath<'static>,
1913        error: AssetLoadError,
1914    },
1915}
1916
1917/// The load state of an asset.
1918#[derive(Component, Clone, Debug)]
1919pub enum LoadState {
1920    /// The asset has not started loading yet
1921    NotLoaded,
1922
1923    /// The asset is in the process of loading.
1924    Loading,
1925
1926    /// The asset has been loaded and has been added to the [`World`]
1927    Loaded,
1928
1929    /// The asset failed to load. The underlying [`AssetLoadError`] is
1930    /// referenced by [`Arc`] clones in all related [`DependencyLoadState`]s
1931    /// and [`RecursiveDependencyLoadState`]s in the asset's dependency tree.
1932    Failed(Arc<AssetLoadError>),
1933}
1934
1935impl LoadState {
1936    /// Returns `true` if this instance is [`LoadState::Loading`]
1937    pub fn is_loading(&self) -> bool {
1938        matches!(self, Self::Loading)
1939    }
1940
1941    /// Returns `true` if this instance is [`LoadState::Loaded`]
1942    pub fn is_loaded(&self) -> bool {
1943        matches!(self, Self::Loaded)
1944    }
1945
1946    /// Returns `true` if this instance is [`LoadState::Failed`]
1947    pub fn is_failed(&self) -> bool {
1948        matches!(self, Self::Failed(_))
1949    }
1950}
1951
1952/// The load state of an asset's dependencies.
1953#[derive(Component, Clone, Debug)]
1954pub enum DependencyLoadState {
1955    /// The asset has not started loading yet
1956    NotLoaded,
1957
1958    /// Dependencies are still loading
1959    Loading,
1960
1961    /// Dependencies have all loaded
1962    Loaded,
1963
1964    /// One or more dependencies have failed to load. The underlying [`AssetLoadError`]
1965    /// is referenced by [`Arc`] clones in all related [`LoadState`] and
1966    /// [`RecursiveDependencyLoadState`]s in the asset's dependency tree.
1967    Failed(Arc<AssetLoadError>),
1968}
1969
1970impl DependencyLoadState {
1971    /// Returns `true` if this instance is [`DependencyLoadState::Loading`]
1972    pub fn is_loading(&self) -> bool {
1973        matches!(self, Self::Loading)
1974    }
1975
1976    /// Returns `true` if this instance is [`DependencyLoadState::Loaded`]
1977    pub fn is_loaded(&self) -> bool {
1978        matches!(self, Self::Loaded)
1979    }
1980
1981    /// Returns `true` if this instance is [`DependencyLoadState::Failed`]
1982    pub fn is_failed(&self) -> bool {
1983        matches!(self, Self::Failed(_))
1984    }
1985}
1986
1987/// The recursive load state of an asset's dependencies.
1988#[derive(Component, Clone, Debug)]
1989pub enum RecursiveDependencyLoadState {
1990    /// The asset has not started loading yet
1991    NotLoaded,
1992
1993    /// Dependencies in this asset's dependency tree are still loading
1994    Loading,
1995
1996    /// Dependencies in this asset's dependency tree have all loaded
1997    Loaded,
1998
1999    /// One or more dependencies have failed to load in this asset's dependency
2000    /// tree. The underlying [`AssetLoadError`] is referenced by [`Arc`] clones
2001    /// in all related [`LoadState`]s and [`DependencyLoadState`]s in the asset's
2002    /// dependency tree.
2003    Failed(Arc<AssetLoadError>),
2004}
2005
2006impl RecursiveDependencyLoadState {
2007    /// Returns `true` if this instance is [`RecursiveDependencyLoadState::Loading`]
2008    pub fn is_loading(&self) -> bool {
2009        matches!(self, Self::Loading)
2010    }
2011
2012    /// Returns `true` if this instance is [`RecursiveDependencyLoadState::Loaded`]
2013    pub fn is_loaded(&self) -> bool {
2014        matches!(self, Self::Loaded)
2015    }
2016
2017    /// Returns `true` if this instance is [`RecursiveDependencyLoadState::Failed`]
2018    pub fn is_failed(&self) -> bool {
2019        matches!(self, Self::Failed(_))
2020    }
2021}
2022
2023/// An error that occurs during an [`Asset`] load.
2024#[derive(Error, Debug, Clone)]
2025#[expect(
2026    missing_docs,
2027    reason = "Adding docs to the variants would not add information beyond the error message and the names"
2028)]
2029pub enum AssetLoadError {
2030    #[error("Requested handle of type {requested:?} for asset '{path}' does not match actual asset type '{actual_asset_name}', which used loader '{loader_name}'")]
2031    RequestedHandleTypeMismatch {
2032        path: AssetPath<'static>,
2033        requested: TypeId,
2034        actual_asset_name: &'static str,
2035        loader_name: &'static str,
2036    },
2037    #[error("Could not find an asset loader matching: Loader Name: {loader_name:?}; Asset Type: {asset_type_id:?}; Extension: {extension:?}; Path: {asset_path:?};")]
2038    MissingAssetLoader {
2039        loader_name: Option<String>,
2040        asset_type_id: Option<TypeId>,
2041        extension: Option<String>,
2042        asset_path: Option<String>,
2043    },
2044    #[error(transparent)]
2045    MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
2046    #[error(transparent)]
2047    MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
2048    #[error(transparent)]
2049    MissingAssetLoaderForTypeIdError(#[from] MissingAssetLoaderForTypeIdError),
2050    #[error(transparent)]
2051    AssetReaderError(#[from] AssetReaderError),
2052    #[error(transparent)]
2053    MissingAssetSourceError(#[from] MissingAssetSourceError),
2054    #[error(transparent)]
2055    MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
2056    #[error("Encountered an error while reading asset metadata bytes")]
2057    AssetMetaReadError,
2058    #[error("Failed to deserialize meta for asset {path}: {error}")]
2059    DeserializeMeta {
2060        path: AssetPath<'static>,
2061        error: Box<DeserializeMetaError>,
2062    },
2063    #[error("Asset '{path}' is configured to be processed. It cannot be loaded directly.")]
2064    #[from(ignore)]
2065    CannotLoadProcessedAsset { path: AssetPath<'static> },
2066    #[error("Asset '{path}' is configured to be ignored. It cannot be loaded.")]
2067    #[from(ignore)]
2068    CannotLoadIgnoredAsset { path: AssetPath<'static> },
2069    #[error("Failed to load asset '{path}', asset loader '{loader_name}' panicked")]
2070    AssetLoaderPanic {
2071        path: AssetPath<'static>,
2072        loader_name: &'static str,
2073    },
2074    #[error(transparent)]
2075    AssetLoaderError(#[from] AssetLoaderError),
2076    #[error(transparent)]
2077    AddAsyncError(#[from] AddAsyncError),
2078    #[error("The file at '{}' does not contain the labeled asset '{}'; it contains the following {} assets: {}",
2079            base_path,
2080            label,
2081            all_labels.len(),
2082            all_labels.iter().map(|l| format!("'{l}'")).collect::<Vec<_>>().join(", "))]
2083    MissingLabel {
2084        base_path: AssetPath<'static>,
2085        label: String,
2086        all_labels: Vec<String>,
2087    },
2088}
2089
2090/// An error that can occur during asset loading.
2091#[derive(Error, Debug, Clone)]
2092#[error("Failed to load asset '{path}' with asset loader '{loader_name}': {error}")]
2093pub struct AssetLoaderError {
2094    path: AssetPath<'static>,
2095    loader_name: &'static str,
2096    error: Arc<BevyError>,
2097}
2098
2099impl AssetLoaderError {
2100    /// The path of the asset that failed to load.
2101    pub fn path(&self) -> &AssetPath<'static> {
2102        &self.path
2103    }
2104
2105    /// The error the loader reported when attempting to load the asset.
2106    ///
2107    /// If you know the type of the error the asset loader returned, you can use
2108    /// [`BevyError::downcast_ref()`] to get it.
2109    pub fn error(&self) -> &BevyError {
2110        &self.error
2111    }
2112}
2113
2114/// An error that occurs while resolving an asset added by `add_async`.
2115#[derive(Error, Debug, Clone)]
2116#[error("An error occurred while resolving an asset added by `add_async`: {error}")]
2117pub struct AddAsyncError {
2118    error: Arc<dyn core::error::Error + Send + Sync + 'static>,
2119}
2120
2121/// An error that occurs when an [`AssetLoader`] is not registered for a given extension.
2122#[derive(Error, Debug, Clone, PartialEq, Eq)]
2123#[error("no `AssetLoader` found{}", format_missing_asset_ext(extensions))]
2124pub struct MissingAssetLoaderForExtensionError {
2125    extensions: Vec<String>,
2126}
2127
2128/// An error that occurs when an [`AssetLoader`] is not registered for a given [`core::any::type_name`].
2129#[derive(Error, Debug, Clone, PartialEq, Eq)]
2130#[error("no `AssetLoader` found with the name '{type_name}'")]
2131pub struct MissingAssetLoaderForTypeNameError {
2132    /// The type name that was not found.
2133    pub type_name: String,
2134}
2135
2136/// An error that occurs when an [`AssetLoader`] is not registered for a given [`Asset`] [`TypeId`].
2137#[derive(Error, Debug, Clone, PartialEq, Eq)]
2138#[error("no `AssetLoader` found with the ID '{type_id:?}'")]
2139pub struct MissingAssetLoaderForTypeIdError {
2140    /// The type ID that was not found.
2141    pub type_id: TypeId,
2142}
2143
2144fn format_missing_asset_ext(exts: &[String]) -> String {
2145    if !exts.is_empty() {
2146        format!(
2147            " for the following extension{}: {}",
2148            if exts.len() > 1 { "s" } else { "" },
2149            exts.join(", ")
2150        )
2151    } else {
2152        " for file with no extension".to_string()
2153    }
2154}
2155
2156impl core::fmt::Debug for AssetServer {
2157    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2158        f.debug_struct("AssetServer")
2159            .field("info", &self.data.infos.read())
2160            .finish()
2161    }
2162}
2163
2164/// This is appended to asset sources when loading a [`LoadedUntypedAsset`]. This provides a unique
2165/// source for a given [`AssetPath`].
2166const UNTYPED_SOURCE_SUFFIX: &str = "--untyped";
2167
2168/// An error when attempting to wait asynchronously for an [`Asset`] to load.
2169#[derive(Error, Debug, Clone)]
2170pub enum WaitForAssetError {
2171    /// The asset is not being loaded; waiting for it is meaningless.
2172    #[error("tried to wait for an asset that is not being loaded")]
2173    NotLoaded,
2174    /// The asset failed to load.
2175    #[error(transparent)]
2176    Failed(Arc<AssetLoadError>),
2177    /// A dependency of the asset failed to load.
2178    #[error(transparent)]
2179    DependencyFailed(Arc<AssetLoadError>),
2180}
2181
2182#[derive(Error, Debug)]
2183pub enum WriteDefaultMetaError {
2184    #[error(transparent)]
2185    MissingAssetLoader(#[from] MissingAssetLoaderForExtensionError),
2186    #[error(transparent)]
2187    MissingAssetSource(#[from] MissingAssetSourceError),
2188    #[error(transparent)]
2189    MissingAssetWriter(#[from] MissingAssetWriterError),
2190    #[error("failed to write default asset meta file: {0}")]
2191    FailedToWriteMeta(#[from] AssetWriterError),
2192    #[error("asset meta file already exists, so avoiding overwrite")]
2193    MetaAlreadyExists,
2194    #[error("encountered an I/O error while reading the existing meta file: {0}")]
2195    IoErrorFromExistingMetaCheck(Arc<std::io::Error>),
2196    #[error("encountered HTTP status {0} when reading the existing meta file")]
2197    HttpErrorFromExistingMetaCheck(u16),
2198}