Skip to main content

bevy_asset/server/
mod.rs

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