bevy_asset/server/
mod.rs

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