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