bevy_asset/server/
mod.rs

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