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