Skip to main content

bevy_asset/
lib.rs

1//! In the context of game development, an "asset" is a piece of content that is loaded from disk and displayed in the game.
2//! Typically, these are authored by artists and designers (in contrast to code),
3//! are relatively large in size, and include everything from textures and models to sounds and music to levels and scripts.
4//!
5//! This presents two main challenges:
6//! - Assets take up a lot of memory; simply storing a copy for each instance of an asset in the game would be prohibitively expensive.
7//! - Loading assets from disk is slow, and can cause long load times and delays.
8//!
9//! These problems play into each other, for if assets are expensive to store in memory,
10//! then larger game worlds will need to load them from disk as needed, ideally without a loading screen.
11//!
12//! As is common in Rust, non-blocking asset loading is done using `async`, with background tasks used to load assets while the game is running.
13//! Bevy coordinates these tasks using the [`AssetServer`] resource, storing each loaded asset in a strongly-typed [`Assets<T>`] collection (also a resource).
14//! [`Handle`]s serve as an id-based reference to entries in the [`Assets`] collection, allowing them to be cheaply shared between systems,
15//! and providing a way to initialize objects (generally entities) before the required assets are loaded.
16//! In short: [`Handle`]s are not the assets themselves, they just tell how to look them up!
17//!
18//! ## Loading assets
19//!
20//! The [`AssetServer`] is the main entry point for loading assets.
21//! Typically, you'll use the [`AssetServer::load`] method to load an asset from disk, which returns a [`Handle`].
22//! Note that this method does not attempt to reload the asset if it has already been loaded: as long as at least one handle has not been dropped,
23//! calling [`AssetServer::load`] on the same path will return the same handle.
24//! The handle that's returned can be used to instantiate various [`Component`]s that require asset data to function,
25//! which will then be spawned into the world as part of an entity.
26//!
27//! To avoid assets "popping" into existence, you may want to check that all of the required assets are loaded before transitioning to a new scene.
28//! This can be done by checking the [`LoadState`] of the asset handle using [`AssetServer::is_loaded_with_dependencies`],
29//! which will be `true` when the asset is ready to use.
30//!
31//! Keep track of what you're waiting on by using a [`HashSet`] of asset handles or similar data structure,
32//! which iterate over and poll in your update loop, and transition to the new scene once all assets are loaded.
33//! Bevy's built-in states system can be very helpful for this!
34//!
35//! # Modifying entities that use assets
36//!
37//! If we later want to change the asset data a given component uses (such as changing an entity's material), we have three options:
38//!
39//! 1. Change the handle stored on the responsible component to the handle of a different asset
40//! 2. Despawn the entity and spawn a new one with the new asset data.
41//! 3. Use the [`Assets`] collection to directly modify the current handle's asset data
42//!
43//! The first option is the most common: just query for the component that holds the handle, and mutate it, pointing to the new asset.
44//! Check how the handle was passed in to the entity when it was spawned: if a mesh-related component required a handle to a mesh asset,
45//! you'll need to find that component via a query and change the handle to the new mesh asset.
46//! This is so commonly done that you should think about strategies for how to store and swap handles in your game.
47//!
48//! The second option is the simplest, but can be slow if done frequently,
49//! and can lead to frustrating bugs as references to the old entity (such as what is targeting it) and other data on the entity are lost.
50//! Generally, this isn't a great strategy.
51//!
52//! The third option has different semantics: rather than modifying the asset data for a single entity, it modifies the asset data for *all* entities using this handle.
53//! While this might be what you want, it generally isn't!
54//!
55//! # Hot reloading assets
56//!
57//! Bevy supports asset hot reloading, allowing you to change assets on disk and see the changes reflected in your game without restarting.
58//! When enabled, any changes to the underlying asset file will be detected by the [`AssetServer`], which will then reload the asset,
59//! mutating the asset data in the [`Assets`] collection and thus updating all entities that use the asset.
60//! While it has limited uses in published games, it is very useful when developing, as it allows you to iterate quickly.
61//!
62//! To enable asset hot reloading on desktop platforms, enable `bevy`'s `file_watcher` cargo feature.
63//! To toggle it at runtime, you can use the `watch_for_changes_override` field in the [`AssetPlugin`] to enable or disable hot reloading.
64//!
65//! # Procedural asset creation
66//!
67//! Not all assets are loaded from disk: some are generated at runtime, such as procedural materials, sounds or even levels.
68//! After creating an item of a type that implements [`Asset`], you can add it to the [`Assets`] collection using [`Assets::add`].
69//! Once in the asset collection, this data can be operated on like any other asset.
70//!
71//! Note that, unlike assets loaded from a file path, no general mechanism currently exists to deduplicate procedural assets:
72//! calling [`Assets::add`] for every entity that needs the asset will create a new copy of the asset for each entity,
73//! quickly consuming memory.
74//!
75//! ## Handles and reference counting
76//!
77//! [`Handle`] (or their untyped counterpart [`UntypedHandle`]) are used to reference assets in the [`Assets`] collection,
78//! and are the primary way to interact with assets in Bevy.
79//! As a user, you'll be working with handles a lot!
80//!
81//! The most important thing to know about handles is that they are reference counted: when you clone a handle, you're incrementing a reference count.
82//! When the object holding the handle is dropped (generally because an entity was despawned), the reference count is decremented.
83//! When the reference count hits zero, the asset it references is removed from the [`Assets`] collection.
84//!
85//! This reference counting is a simple, largely automatic way to avoid holding onto memory for game objects that are no longer in use.
86//! However, it can lead to surprising behavior if you're not careful!
87//!
88//! There are two categories of problems to watch out for:
89//! - never dropping a handle, causing the asset to never be removed from memory
90//! - dropping a handle too early, causing the asset to be removed from memory while it's still in use
91//!
92//! The first problem is less critical for beginners, as for tiny games, you can often get away with simply storing all of the assets in memory at once,
93//! and loading them all at the start of the game.
94//! As your game grows, you'll need to be more careful about when you load and unload assets,
95//! segmenting them by level or area, and loading them on-demand.
96//! This problem generally arises when handles are stored in a persistent "collection" or "manifest" of possible objects (generally in a resource),
97//! which is convenient for easy access and zero-latency spawning, but can result in high but stable memory usage.
98//!
99//! The second problem is more concerning, and looks like your models or textures suddenly disappearing from the game.
100//! Debugging reveals that the *entities* are still there, but nothing is rendering!
101//! This is because the assets were removed from memory while they were still in use.
102//! You were probably too aggressive with the use of weak handles (which don't increment the reference count of the asset): think through the lifecycle of your assets carefully!
103//! As soon as an asset is loaded, you must ensure that at least one strong handle is held to it until all matching entities are out of sight of the player.
104//!
105//! # Asset dependencies
106//!
107//! Some assets depend on other assets to be loaded before they can be loaded themselves.
108//! For example, a 3D model might require both textures and meshes to be loaded,
109//! or a 2D level might require a tileset to be loaded.
110//!
111//! The assets that are required to load another asset are called "dependencies".
112//! An asset is only considered fully loaded when it and all of its dependencies are loaded.
113//! Asset dependencies can be declared when implementing the [`Asset`] trait by implementing the [`VisitAssetDependencies`] trait,
114//! and the `#[dependency]` attribute can be used to automatically derive this implementation.
115//!
116//! # Custom asset types
117//!
118//! While Bevy comes with implementations for a large number of common game-oriented asset types (often behind off-by-default feature flags!),
119//! implementing a custom asset type can be useful when dealing with unusual, game-specific, or proprietary formats.
120//!
121//! Defining a new asset type is as simple as implementing the [`Asset`] trait.
122//! This requires [`TypePath`] for metadata about the asset type,
123//! and [`VisitAssetDependencies`] to track asset dependencies.
124//! In simple cases, you can derive [`Asset`] and [`Reflect`] and be done with it: the required supertraits will be implemented for you.
125//!
126//! With a new asset type in place, we now need to figure out how to load it.
127//! While [`AssetReader`](io::AssetReader) describes strategies to read asset bytes from various sources,
128//! [`AssetLoader`] is the trait that actually turns those into your desired in-memory format.
129//! Generally, (only) [`AssetLoader`] needs to be implemented for custom assets, as the [`AssetReader`](io::AssetReader) implementations are provided by Bevy.
130//!
131//! However, [`AssetLoader`] shouldn't be implemented for your asset type directly: instead, this is implemented for a "loader" type
132//! that can store settings and any additional data required to load your asset, while your asset type is used as the [`AssetLoader::Asset`] associated type.
133//! As the trait documentation explains, this allows various [`AssetLoader::Settings`] to be used to configure the loader.
134//!
135//! After the loader is implemented, it needs to be registered with the [`AssetServer`] using [`App::register_asset_loader`](AssetApp::register_asset_loader).
136//! Once your asset type is loaded, you can use it in your game like any other asset type!
137//!
138
139#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
140#![cfg_attr(docsrs, feature(doc_cfg))]
141#![doc(
142    html_logo_url = "https://bevy.org/assets/icon.png",
143    html_favicon_url = "https://bevy.org/assets/icon.png"
144)]
145#![no_std]
146
147extern crate alloc;
148extern crate std;
149
150// Required to make proc macros work in bevy itself.
151extern crate self as bevy_asset;
152
153pub mod io;
154pub mod meta;
155pub mod processor;
156pub mod saver;
157pub mod transformer;
158
159/// The asset prelude.
160///
161/// This includes the most common types in this crate, re-exported for your convenience.
162pub mod prelude {
163    #[doc(hidden)]
164    pub use crate::asset_changed::AssetChanged;
165
166    #[doc(hidden)]
167    pub use crate::{
168        asset_value, Asset, AssetApp, AssetEvent, AssetId, AssetMode, AssetPlugin, AssetServer,
169        Assets, DirectAssetAccessExt, Handle, UntypedHandle,
170    };
171}
172
173mod asset_changed;
174mod assets;
175mod direct_access_ext;
176mod event;
177mod folder;
178mod handle;
179mod id;
180mod loader;
181mod loader_builders;
182mod path;
183mod reflect;
184mod render_asset;
185mod server;
186
187pub use assets::*;
188pub use bevy_asset_macros::{Asset, VisitAssetDependencies};
189use bevy_diagnostic::{Diagnostic, DiagnosticsStore, RegisterDiagnostic};
190pub use direct_access_ext::DirectAssetAccessExt;
191pub use event::*;
192pub use folder::*;
193pub use futures_lite::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
194pub use handle::*;
195pub use id::*;
196pub use loader::*;
197pub use loader_builders::NestedLoadBuilder;
198pub use path::*;
199pub use reflect::*;
200pub use render_asset::*;
201pub use server::*;
202
203pub use uuid;
204
205use crate::{
206    io::{embedded::EmbeddedAssetRegistry, AssetSourceBuilder, AssetSourceBuilders, AssetSourceId},
207    processor::{AssetProcessor, Process},
208};
209use alloc::{
210    string::{String, ToString},
211    sync::Arc,
212    vec::Vec,
213};
214use bevy_app::{App, Plugin, PostUpdate, PreUpdate};
215use bevy_ecs::{prelude::Component, schedule::common_conditions::resource_exists};
216use bevy_ecs::{
217    reflect::AppTypeRegistry,
218    schedule::{IntoScheduleConfigs, SystemSet},
219    world::FromWorld,
220};
221use bevy_platform::collections::{HashMap, HashSet};
222use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath};
223use core::any::TypeId;
224use tracing::error;
225
226/// Provides "asset" loading and processing functionality. An [`Asset`] is a "runtime value" that is loaded from an [`AssetSource`],
227/// which can be something like a filesystem, a network, etc.
228///
229/// Supports flexible "modes", such as [`AssetMode::Processed`] and
230/// [`AssetMode::Unprocessed`] that enable using the asset workflow that best suits your project.
231///
232/// [`AssetSource`]: io::AssetSource
233pub struct AssetPlugin {
234    /// The default file path to use (relative to the project root) for unprocessed assets.
235    pub file_path: String,
236    /// The default file path to use (relative to the project root) for processed assets.
237    pub processed_file_path: String,
238    /// If set, will override the default "watch for changes" setting. By default "watch for changes" will be `false` unless
239    /// the `watch` cargo feature is set. `watch` can be enabled manually, or it will be automatically enabled if a specific watcher
240    /// like `file_watcher` is enabled.
241    ///
242    /// Most use cases should leave this set to [`None`] and enable a specific watcher feature such as `file_watcher` to enable
243    /// watching for dev-scenarios.
244    pub watch_for_changes_override: Option<bool>,
245    /// If set, will override the default "use asset processor" setting. By default "use asset
246    /// processor" will be `false` unless the `asset_processor` cargo feature is set.
247    ///
248    /// Most use cases should leave this set to [`None`] and enable the `asset_processor` cargo
249    /// feature.
250    pub use_asset_processor_override: Option<bool>,
251    /// The [`AssetMode`] to use for this server.
252    pub mode: AssetMode,
253    /// How/If asset meta files should be checked.
254    pub meta_check: AssetMetaCheck,
255    /// How to handle load requests of files that are outside the approved directories.
256    ///
257    /// Approved folders are [`AssetPlugin::file_path`] and the folder of each
258    /// [`AssetSource`](io::AssetSource). Subfolders within these folders are also valid.
259    pub unapproved_path_mode: UnapprovedPathMode,
260}
261
262/// Determines how to react to attempts to load assets not inside the approved folders.
263///
264/// Approved folders are [`AssetPlugin::file_path`] and the folder of each
265/// [`AssetSource`](io::AssetSource). Subfolders within these folders are also valid.
266///
267/// It is strongly discouraged to use [`Allow`](UnapprovedPathMode::Allow) if your
268/// app will include scripts or modding support, as it could allow arbitrary file
269/// access for malicious code.
270///
271/// The default value is [`Forbid`](UnapprovedPathMode::Forbid).
272///
273/// See [`AssetPath::is_unapproved`](crate::AssetPath::is_unapproved)
274#[derive(Clone, Default)]
275pub enum UnapprovedPathMode {
276    /// Unapproved asset loading is allowed. This is strongly discouraged.
277    Allow,
278    /// Fails to load any asset that is unapproved, unless [`LoadBuilder::override_unapproved`] is
279    /// used.
280    Deny,
281    /// Fails to load any asset that is unapproved.
282    #[default]
283    Forbid,
284}
285
286/// Controls whether or not assets are pre-processed before being loaded.
287///
288/// This setting is controlled by setting [`AssetPlugin::mode`].
289///
290/// When building on web, asset preprocessing can cause problems due to the lack of filesystem access.
291/// See [bevy#10157](https://github.com/bevyengine/bevy/issues/10157) for context.
292#[derive(Debug)]
293pub enum AssetMode {
294    /// Loads assets from their [`AssetSource`]'s default [`AssetReader`] without any "preprocessing".
295    ///
296    /// [`AssetReader`]: io::AssetReader
297    /// [`AssetSource`]: io::AssetSource
298    Unprocessed,
299    /// Assets will be "pre-processed". This enables assets to be imported / converted / optimized ahead of time.
300    ///
301    /// Assets will be read from their unprocessed [`AssetSource`] (defaults to the `assets` folder),
302    /// processed according to their [`AssetMeta`], and written to their processed [`AssetSource`] (defaults to the `imported_assets/Default` folder).
303    ///
304    /// By default, this assumes the processor _has already been run_. It will load assets from their final processed [`AssetReader`].
305    ///
306    /// When developing an app, you should enable the `asset_processor` cargo feature, which will run the asset processor at startup. This should generally
307    /// be used in combination with the `file_watcher` cargo feature, which enables hot-reloading of assets that have changed. When both features are enabled,
308    /// changes to "original/source assets" will be detected, the asset will be re-processed, and then the final processed asset will be hot-reloaded in the app.
309    ///
310    /// [`AssetMeta`]: meta::AssetMeta
311    /// [`AssetSource`]: io::AssetSource
312    /// [`AssetReader`]: io::AssetReader
313    Processed,
314}
315
316/// Configures how / if meta files will be checked. If an asset's meta file is not checked, the default meta for the asset
317/// will be used.
318#[derive(Debug, Default, Clone)]
319pub enum AssetMetaCheck {
320    /// Always check if assets have meta files. If the meta does not exist, the default meta will be used.
321    #[default]
322    Always,
323    /// Only look up meta files for the provided paths. The default meta will be used for any paths not contained in this set.
324    Paths(HashSet<AssetPath<'static>>),
325    /// Never check if assets have meta files and always use the default meta. If meta files exist, they will be ignored and the default meta will be used.
326    Never,
327}
328
329impl Default for AssetPlugin {
330    fn default() -> Self {
331        Self {
332            mode: AssetMode::Unprocessed,
333            file_path: Self::DEFAULT_UNPROCESSED_FILE_PATH.to_string(),
334            processed_file_path: Self::DEFAULT_PROCESSED_FILE_PATH.to_string(),
335            watch_for_changes_override: None,
336            use_asset_processor_override: None,
337            meta_check: AssetMetaCheck::default(),
338            unapproved_path_mode: UnapprovedPathMode::default(),
339        }
340    }
341}
342
343impl AssetPlugin {
344    const DEFAULT_UNPROCESSED_FILE_PATH: &'static str = "assets";
345    /// NOTE: this is in the Default sub-folder to make this forward compatible with "import profiles"
346    /// and to allow us to put the "processor transaction log" at `imported_assets/log`
347    const DEFAULT_PROCESSED_FILE_PATH: &'static str = "imported_assets/Default";
348}
349
350impl Plugin for AssetPlugin {
351    fn build(&self, app: &mut App) {
352        let embedded = EmbeddedAssetRegistry::default();
353        {
354            let mut sources = app
355                .world_mut()
356                .get_resource_or_init::<AssetSourceBuilders>();
357            sources.init_default_source(
358                &self.file_path,
359                (!matches!(self.mode, AssetMode::Unprocessed))
360                    .then_some(self.processed_file_path.as_str()),
361            );
362            embedded.register_source(&mut sources);
363        }
364        {
365            let watch = self
366                .watch_for_changes_override
367                .unwrap_or(cfg!(feature = "watch"));
368            match self.mode {
369                AssetMode::Unprocessed => {
370                    let mut builders = app.world_mut().resource_mut::<AssetSourceBuilders>();
371                    let sources = builders.build_sources(watch, false);
372
373                    app.insert_resource(AssetServer::new_with_meta_check(
374                        Arc::new(sources),
375                        AssetServerMode::Unprocessed,
376                        self.meta_check.clone(),
377                        watch,
378                        self.unapproved_path_mode.clone(),
379                    ));
380                }
381                AssetMode::Processed => {
382                    let use_asset_processor = self
383                        .use_asset_processor_override
384                        .unwrap_or(cfg!(feature = "asset_processor"));
385                    if use_asset_processor {
386                        let mut builders = app.world_mut().resource_mut::<AssetSourceBuilders>();
387                        let (processor, sources) = AssetProcessor::new(&mut builders, watch);
388                        // the main asset server shares loaders with the processor asset server
389                        app.insert_resource(AssetServer::new_with_loaders(
390                            sources,
391                            processor.server().data.loaders.clone(),
392                            AssetServerMode::Processed,
393                            AssetMetaCheck::Always,
394                            watch,
395                            self.unapproved_path_mode.clone(),
396                        ))
397                        .insert_resource(processor)
398                        .add_systems(bevy_app::Startup, AssetProcessor::start);
399                    } else {
400                        let mut builders = app.world_mut().resource_mut::<AssetSourceBuilders>();
401                        let sources = builders.build_sources(false, watch);
402                        app.insert_resource(AssetServer::new_with_meta_check(
403                            Arc::new(sources),
404                            AssetServerMode::Processed,
405                            AssetMetaCheck::Always,
406                            watch,
407                            self.unapproved_path_mode.clone(),
408                        ));
409                    }
410                }
411            }
412        }
413        app.insert_resource(embedded)
414            .init_asset::<LoadedFolder>()
415            .init_asset::<LoadedUntypedAsset>()
416            .init_asset::<()>()
417            .add_message::<UntypedAssetLoadFailedEvent>()
418            .configure_sets(
419                PreUpdate,
420                AssetTrackingSystems.after(handle_internal_asset_events),
421            )
422            // `handle_internal_asset_events` requires the use of `&mut World`,
423            // and as a result has ambiguous system ordering with all other systems in `PreUpdate`.
424            // This is virtually never a real problem: asset loading is async and so anything that interacts directly with it
425            // needs to be robust to stochastic delays anyways.
426            .add_systems(
427                PreUpdate,
428                (
429                    handle_internal_asset_events.ambiguous_with_all(),
430                    // TODO: Remove the run condition and use `If` once
431                    // https://github.com/bevyengine/bevy/issues/21549 is resolved.
432                    publish_asset_server_diagnostics.run_if(resource_exists::<DiagnosticsStore>),
433                )
434                    .chain(),
435            )
436            .register_diagnostic(Diagnostic::new(AssetServer::STARTED_LOAD_COUNT));
437    }
438}
439
440/// Declares that this type is an asset,
441/// which can be loaded and managed by the [`AssetServer`] and stored in [`Assets`] collections.
442///
443/// Generally, assets are large, complex, and/or expensive to load from disk, and are often authored by artists or designers.
444///
445/// [`TypePath`] is largely used for diagnostic purposes, and should almost always be implemented by deriving [`Reflect`] on your type.
446/// [`VisitAssetDependencies`] is used to track asset dependencies, and an implementation is automatically generated when deriving [`Asset`].
447#[diagnostic::on_unimplemented(
448    message = "`{Self}` is not an `Asset`",
449    label = "invalid `Asset`",
450    note = "consider annotating `{Self}` with `#[derive(Asset)]`"
451)]
452pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + 'static {}
453
454/// A trait for components that can be used as asset identifiers, e.g. handle wrappers.
455pub trait AsAssetId: Component {
456    /// The underlying asset type.
457    type Asset: Asset;
458
459    /// Retrieves the asset id from this component.
460    fn as_asset_id(&self) -> AssetId<Self::Asset>;
461}
462
463/// This trait defines how to visit the dependencies of an asset.
464/// For example, a 3D model might require both textures and meshes to be loaded.
465///
466/// Note that this trait is automatically implemented when deriving [`Asset`].
467pub trait VisitAssetDependencies {
468    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId));
469}
470
471impl<A: Asset> VisitAssetDependencies for Handle<A> {
472    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
473        visit(self.id().untyped());
474    }
475}
476
477impl<A: Asset> VisitAssetDependencies for Option<Handle<A>> {
478    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
479        if let Some(handle) = self {
480            visit(handle.id().untyped());
481        }
482    }
483}
484
485impl VisitAssetDependencies for UntypedHandle {
486    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
487        visit(self.id());
488    }
489}
490
491impl VisitAssetDependencies for Option<UntypedHandle> {
492    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
493        if let Some(handle) = self {
494            visit(handle.id());
495        }
496    }
497}
498
499impl VisitAssetDependencies for UntypedAssetId {
500    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
501        visit(*self);
502    }
503}
504
505impl<A: Asset, const N: usize> VisitAssetDependencies for [Handle<A>; N] {
506    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
507        for dependency in self {
508            visit(dependency.id().untyped());
509        }
510    }
511}
512
513impl<const N: usize> VisitAssetDependencies for [UntypedHandle; N] {
514    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
515        for dependency in self {
516            visit(dependency.id());
517        }
518    }
519}
520
521impl<V: VisitAssetDependencies> VisitAssetDependencies for Vec<V> {
522    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
523        for dependency in self {
524            dependency.visit_dependencies(visit);
525        }
526    }
527}
528
529impl<V: VisitAssetDependencies> VisitAssetDependencies for HashSet<V> {
530    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
531        for dependency in self {
532            dependency.visit_dependencies(visit);
533        }
534    }
535}
536
537impl<A: Asset, K> VisitAssetDependencies for HashMap<K, Handle<A>> {
538    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
539        for dependency in self.values() {
540            visit(dependency.id().untyped());
541        }
542    }
543}
544
545impl<K> VisitAssetDependencies for HashMap<K, UntypedHandle> {
546    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
547        for dependency in self.values() {
548            visit(dependency.id());
549        }
550    }
551}
552
553/// Adds asset-related builder methods to [`App`].
554pub trait AssetApp {
555    /// Registers the given `loader` in the [`App`]'s [`AssetServer`].
556    fn register_asset_loader<L: AssetLoader>(&mut self, loader: L) -> &mut Self;
557    /// Registers the given `processor` in the [`App`]'s [`AssetProcessor`].
558    fn register_asset_processor<P: Process>(&mut self, processor: P) -> &mut Self;
559    /// Registers the given [`AssetSourceBuilder`] with the given `id`.
560    ///
561    /// Note that asset sources must be registered before adding [`AssetPlugin`] to your application,
562    /// since registered asset sources are built at that point and not after.
563    fn register_asset_source(
564        &mut self,
565        id: impl Into<AssetSourceId<'static>>,
566        source: AssetSourceBuilder,
567    ) -> &mut Self;
568    /// Sets the default asset processor for the given `extension`.
569    fn set_default_asset_processor<P: Process>(&mut self, extension: &str) -> &mut Self;
570    /// Initializes the given loader in the [`App`]'s [`AssetServer`].
571    fn init_asset_loader<L: AssetLoader + FromWorld>(&mut self) -> &mut Self;
572    /// Initializes the given [`Asset`] in the [`App`] by:
573    /// * Registering the [`Asset`] in the [`AssetServer`]
574    /// * Initializing the [`AssetEvent`] resource for the [`Asset`]
575    /// * Adding other relevant systems and resources for the [`Asset`]
576    /// * Ignoring schedule ambiguities in [`Assets`] resource. Any time a system takes
577    ///   mutable access to this resource this causes a conflict, but they rarely actually
578    ///   modify the same underlying asset.
579    fn init_asset<A: Asset>(&mut self) -> &mut Self;
580    /// Registers the asset type `T` using `[App::register]`,
581    /// and adds [`ReflectAsset`] type data to `T` and [`ReflectHandle`] type data to [`Handle<T>`] in the type registry.
582    ///
583    /// This enables reflection code to access assets. For detailed information, see the docs on [`ReflectAsset`] and [`ReflectHandle`].
584    fn register_asset_reflect<A>(&mut self) -> &mut Self
585    where
586        A: Asset + Reflect + FromReflect + GetTypeRegistration;
587    /// Preregisters a loader for the given extensions, that will block asset loads until a real loader
588    /// is registered.
589    fn preregister_asset_loader<L: AssetLoader>(&mut self, extensions: &[&str]) -> &mut Self;
590}
591
592impl AssetApp for App {
593    fn register_asset_loader<L: AssetLoader>(&mut self, loader: L) -> &mut Self {
594        self.world()
595            .resource::<AssetServer>()
596            .register_loader(loader);
597        self
598    }
599
600    fn register_asset_processor<P: Process>(&mut self, processor: P) -> &mut Self {
601        if let Some(asset_processor) = self.world().get_resource::<AssetProcessor>() {
602            asset_processor.register_processor(processor);
603        }
604        self
605    }
606
607    fn register_asset_source(
608        &mut self,
609        id: impl Into<AssetSourceId<'static>>,
610        source: AssetSourceBuilder,
611    ) -> &mut Self {
612        let id = id.into();
613        if self.world().get_resource::<AssetServer>().is_some() {
614            error!("{} must be registered before `AssetPlugin` (typically added as part of `DefaultPlugins`)", id);
615        }
616
617        {
618            let mut sources = self
619                .world_mut()
620                .get_resource_or_init::<AssetSourceBuilders>();
621            sources.insert(id, source);
622        }
623
624        self
625    }
626
627    fn set_default_asset_processor<P: Process>(&mut self, extension: &str) -> &mut Self {
628        if let Some(asset_processor) = self.world().get_resource::<AssetProcessor>() {
629            asset_processor.set_default_processor::<P>(extension);
630        }
631        self
632    }
633
634    fn init_asset_loader<L: AssetLoader + FromWorld>(&mut self) -> &mut Self {
635        let loader = L::from_world(self.world_mut());
636        self.register_asset_loader(loader)
637    }
638
639    fn init_asset<A: Asset>(&mut self) -> &mut Self {
640        let assets = Assets::<A>::default();
641        self.world()
642            .resource::<AssetServer>()
643            .register_asset(&assets);
644        if self.world().contains_resource::<AssetProcessor>() {
645            let processor = self.world().resource::<AssetProcessor>();
646            // The processor should have its own handle provider separate from the Asset storage
647            // to ensure the id spaces are entirely separate. Not _strictly_ necessary, but
648            // desirable.
649            processor
650                .server()
651                .register_handle_provider(AssetHandleProvider::new(
652                    TypeId::of::<A>(),
653                    Arc::new(AssetIndexAllocator::default()),
654                ));
655        }
656        self.insert_resource(assets)
657            .allow_ambiguous_resource::<Assets<A>>()
658            .add_message::<AssetEvent<A>>()
659            .add_message::<AssetLoadFailedEvent<A>>()
660            .register_type::<Handle<A>>()
661            .add_systems(
662                PostUpdate,
663                Assets::<A>::asset_events
664                    .run_if(Assets::<A>::asset_events_condition)
665                    .in_set(AssetEventSystems),
666            )
667            .add_systems(
668                PreUpdate,
669                Assets::<A>::track_assets.in_set(AssetTrackingSystems),
670            )
671    }
672
673    fn register_asset_reflect<A>(&mut self) -> &mut Self
674    where
675        A: Asset + Reflect + FromReflect + GetTypeRegistration,
676    {
677        let type_registry = self.world().resource::<AppTypeRegistry>();
678        {
679            let mut type_registry = type_registry.write();
680
681            type_registry.register::<A>();
682            type_registry.register::<Handle<A>>();
683            type_registry.register::<HandleTemplate<A>>();
684            type_registry.register_type_data::<A, ReflectAsset>();
685            type_registry.register_type_data::<Handle<A>, ReflectHandle>();
686            type_registry
687                .register_type_conversion::<String, HandleTemplate<A>, _>(|s| Ok(s.into()));
688        }
689
690        self
691    }
692
693    fn preregister_asset_loader<L: AssetLoader>(&mut self, extensions: &[&str]) -> &mut Self {
694        self.world_mut()
695            .resource_mut::<AssetServer>()
696            .preregister_loader::<L>(extensions);
697        self
698    }
699}
700
701/// A system set that holds all "track asset" operations.
702#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
703pub struct AssetTrackingSystems;
704
705/// A system set where events accumulated in [`Assets`] are applied to the [`AssetEvent`] [`Messages`] resource.
706///
707/// [`Messages`]: bevy_ecs::message::Messages
708#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
709pub struct AssetEventSystems;
710
711#[cfg(test)]
712mod tests {
713    use crate::{
714        folder::LoadedFolder,
715        handle::Handle,
716        io::{
717            gated::{GateOpener, GatedReader},
718            memory::{Dir, MemoryAssetReader, MemoryAssetWriter},
719            AssetReader, AssetReaderError, AssetSourceBuilder, AssetSourceEvent, AssetSourceId,
720            AssetWatcher, Reader,
721        },
722        loader::{AssetLoader, LoadContext},
723        Asset, AssetApp, AssetEvent, AssetId, AssetLoadError, AssetLoadFailedEvent, AssetPath,
724        AssetPlugin, AssetServer, Assets, InvalidGenerationError, LoadState, LoadedAsset,
725        UnapprovedPathMode, UntypedHandle, VisitAssetDependencies, WriteDefaultMetaError,
726    };
727    use alloc::{
728        boxed::Box,
729        format,
730        string::{String, ToString},
731        sync::Arc,
732        vec,
733        vec::Vec,
734    };
735    use async_channel::{Receiver, Sender};
736    use bevy_app::{App, TaskPoolPlugin, Update};
737    use bevy_diagnostic::{DiagnosticsPlugin, DiagnosticsStore};
738    use bevy_ecs::{
739        message::MessageCursor,
740        prelude::*,
741        schedule::{LogLevel, ScheduleBuildSettings},
742    };
743    use bevy_platform::{
744        collections::{HashMap, HashSet},
745        sync::Mutex,
746    };
747    use bevy_reflect::{Reflect, TypePath};
748    use bevy_tasks::block_on;
749    use core::{any::TypeId, time::Duration};
750    use futures_lite::AsyncReadExt;
751    use ron::ser::PrettyConfig;
752    use serde::{Deserialize, Serialize};
753    use std::path::{Path, PathBuf};
754    use thiserror::Error;
755
756    #[derive(Asset, Debug, Default, Reflect)]
757    pub struct CoolText {
758        pub text: String,
759        pub embedded: String,
760        #[dependency]
761        pub dependencies: Vec<Handle<CoolText>>,
762        #[dependency]
763        pub sub_texts: Vec<Handle<SubText>>,
764    }
765
766    #[derive(Asset, TypePath, Debug)]
767    pub struct SubText {
768        pub text: String,
769    }
770
771    #[derive(Serialize, Deserialize, Default)]
772    pub struct CoolTextRon {
773        pub text: String,
774        pub dependencies: Vec<String>,
775        pub embedded_dependencies: Vec<String>,
776        pub sub_texts: Vec<String>,
777    }
778
779    #[derive(Default, TypePath)]
780    pub struct CoolTextLoader;
781
782    #[derive(Error, Debug)]
783    pub enum CoolTextLoaderError {
784        #[error("Could not load dependency: {dependency}")]
785        CannotLoadDependency { dependency: AssetPath<'static> },
786        #[error("A RON error occurred during loading")]
787        RonSpannedError(#[from] ron::error::SpannedError),
788        #[error("An IO error occurred during loading")]
789        Io(#[from] std::io::Error),
790    }
791
792    impl AssetLoader for CoolTextLoader {
793        type Asset = CoolText;
794
795        type Settings = ();
796
797        type Error = CoolTextLoaderError;
798
799        async fn load(
800            &self,
801            reader: &mut dyn Reader,
802            _settings: &Self::Settings,
803            load_context: &mut LoadContext<'_>,
804        ) -> Result<Self::Asset, Self::Error> {
805            let mut bytes = Vec::new();
806            reader.read_to_end(&mut bytes).await?;
807            let mut ron: CoolTextRon = ron::de::from_bytes(&bytes)?;
808            let mut embedded = String::new();
809            for dep in ron.embedded_dependencies {
810                let loaded = load_context
811                    .load_builder()
812                    .load_value::<CoolText>(&dep)
813                    .await
814                    .map_err(|_| Self::Error::CannotLoadDependency {
815                        dependency: dep.into(),
816                    })?;
817                let cool = loaded.get();
818                embedded.push_str(&cool.text);
819            }
820            Ok(CoolText {
821                text: ron.text,
822                embedded,
823                dependencies: ron
824                    .dependencies
825                    .iter()
826                    .map(|p| load_context.load(p))
827                    .collect(),
828                sub_texts: ron
829                    .sub_texts
830                    .drain(..)
831                    .map(|text| load_context.add_labeled_asset(text.clone(), SubText { text }))
832                    .collect(),
833            })
834        }
835
836        fn extensions(&self) -> &[&str] {
837            &["cool.ron"]
838        }
839    }
840
841    /// A dummy [`CoolText`] asset reader that only succeeds after `failure_count` times it's read from for each asset.
842    #[derive(Default, Clone)]
843    pub struct UnstableMemoryAssetReader {
844        pub attempt_counters: Arc<Mutex<HashMap<Box<Path>, usize>>>,
845        pub load_delay: Duration,
846        memory_reader: MemoryAssetReader,
847        failure_count: usize,
848    }
849
850    impl UnstableMemoryAssetReader {
851        pub fn new(root: Dir, failure_count: usize) -> Self {
852            Self {
853                load_delay: Duration::from_millis(10),
854                memory_reader: MemoryAssetReader { root },
855                attempt_counters: Default::default(),
856                failure_count,
857            }
858        }
859    }
860
861    impl AssetReader for UnstableMemoryAssetReader {
862        async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
863            self.memory_reader.is_directory(path).await
864        }
865        async fn read_directory<'a>(
866            &'a self,
867            path: &'a Path,
868        ) -> Result<Box<bevy_asset::io::PathStream>, AssetReaderError> {
869            self.memory_reader.read_directory(path).await
870        }
871        async fn read_meta<'a>(
872            &'a self,
873            path: &'a Path,
874        ) -> Result<impl Reader + 'a, AssetReaderError> {
875            self.memory_reader.read_meta(path).await
876        }
877        async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
878            let attempt_number = {
879                let mut attempt_counters = self.attempt_counters.lock().unwrap();
880                if let Some(existing) = attempt_counters.get_mut(path) {
881                    *existing += 1;
882                    *existing
883                } else {
884                    attempt_counters.insert(path.into(), 1);
885                    1
886                }
887            };
888
889            if attempt_number <= self.failure_count {
890                let io_error = std::io::Error::new(
891                    std::io::ErrorKind::ConnectionRefused,
892                    format!(
893                        "Simulated failure {attempt_number} of {}",
894                        self.failure_count
895                    ),
896                );
897                let wait = self.load_delay;
898                return async move {
899                    std::thread::sleep(wait);
900                    Err(AssetReaderError::Io(io_error.into()))
901                }
902                .await;
903            }
904
905            self.memory_reader.read(path).await
906        }
907    }
908
909    /// Creates a basic asset app and an in-memory file system.
910    pub(crate) fn create_app() -> (App, Dir) {
911        let mut app = App::new();
912        let dir = Dir::default();
913        let dir_clone = dir.clone();
914        let dir_clone2 = dir.clone();
915        app.register_asset_source(
916            AssetSourceId::Default,
917            AssetSourceBuilder::new(move || {
918                Box::new(MemoryAssetReader {
919                    root: dir_clone.clone(),
920                })
921            })
922            .with_writer(move |_| {
923                Some(Box::new(MemoryAssetWriter {
924                    root: dir_clone2.clone(),
925                }))
926            }),
927        )
928        .add_plugins((
929            TaskPoolPlugin::default(),
930            AssetPlugin {
931                watch_for_changes_override: Some(false),
932                use_asset_processor_override: Some(false),
933                ..Default::default()
934            },
935            DiagnosticsPlugin,
936        ));
937        (app, dir)
938    }
939
940    fn create_app_with_gate(dir: Dir) -> (App, GateOpener) {
941        let mut app = App::new();
942        let (gated_memory_reader, gate_opener) = GatedReader::new(MemoryAssetReader { root: dir });
943        app.register_asset_source(
944            AssetSourceId::Default,
945            AssetSourceBuilder::new(move || Box::new(gated_memory_reader.clone())),
946        )
947        .add_plugins((
948            TaskPoolPlugin::default(),
949            AssetPlugin {
950                watch_for_changes_override: Some(false),
951                use_asset_processor_override: Some(false),
952                ..Default::default()
953            },
954            DiagnosticsPlugin,
955        ));
956        (app, gate_opener)
957    }
958
959    pub fn run_app_until(app: &mut App, mut predicate: impl FnMut(&mut World) -> Option<()>) {
960        for _ in 0..LARGE_ITERATION_COUNT {
961            app.update();
962            if predicate(app.world_mut()).is_some() {
963                return;
964            }
965        }
966
967        panic!("Ran out of loops to return `Some` from `predicate`");
968    }
969
970    const LARGE_ITERATION_COUNT: usize = 10000;
971
972    fn get<A: Asset>(world: &World, id: AssetId<A>) -> Option<&A> {
973        world.resource::<Assets<A>>().get(id)
974    }
975
976    fn get_started_load_count(world: &World) -> usize {
977        world
978            .resource::<DiagnosticsStore>()
979            .get_measurement(&AssetServer::STARTED_LOAD_COUNT)
980            .map(|measurement| measurement.value as _)
981            .unwrap_or_default()
982    }
983
984    #[derive(Resource, Default)]
985    struct StoredEvents(Vec<AssetEvent<CoolText>>);
986
987    fn store_asset_events(
988        mut reader: MessageReader<AssetEvent<CoolText>>,
989        mut storage: ResMut<StoredEvents>,
990    ) {
991        storage.0.extend(reader.read().cloned());
992    }
993
994    /// Serializes `text` into a `CoolText` that can be loaded.
995    ///
996    /// This doesn't support all the features of `CoolText`, so more complex scenarios may require
997    /// doing this manually.
998    pub(crate) fn serialize_as_cool_text(text: &str) -> String {
999        let cool_text_ron = CoolTextRon {
1000            text: text.into(),
1001            dependencies: vec![],
1002            embedded_dependencies: vec![],
1003            sub_texts: vec![],
1004        };
1005        ron::ser::to_string_pretty(&cool_text_ron, PrettyConfig::new().new_line("\n")).unwrap()
1006    }
1007
1008    #[test]
1009    fn load_dependencies() {
1010        let dir = Dir::default();
1011
1012        let a_path = "a.cool.ron";
1013        let a_ron = r#"
1014(
1015    text: "a",
1016    dependencies: [
1017        "foo/b.cool.ron",
1018        "c.cool.ron",
1019    ],
1020    embedded_dependencies: [],
1021    sub_texts: [],
1022)"#;
1023        let b_path = "foo/b.cool.ron";
1024        let b_ron = r#"
1025(
1026    text: "b",
1027    dependencies: [],
1028    embedded_dependencies: [],
1029    sub_texts: [],
1030)"#;
1031
1032        let c_path = "c.cool.ron";
1033        let c_ron = r#"
1034(
1035    text: "c",
1036    dependencies: [
1037        "d.cool.ron",
1038    ],
1039    embedded_dependencies: ["a.cool.ron", "foo/b.cool.ron"],
1040    sub_texts: ["hello"],
1041)"#;
1042
1043        let d_path = "d.cool.ron";
1044        let d_ron = r#"
1045(
1046    text: "d",
1047    dependencies: [],
1048    embedded_dependencies: [],
1049    sub_texts: [],
1050)"#;
1051
1052        dir.insert_asset_text(Path::new(a_path), a_ron);
1053        dir.insert_asset_text(Path::new(b_path), b_ron);
1054        dir.insert_asset_text(Path::new(c_path), c_ron);
1055        dir.insert_asset_text(Path::new(d_path), d_ron);
1056
1057        #[derive(Resource)]
1058        struct IdResults {
1059            b_id: AssetId<CoolText>,
1060            c_id: AssetId<CoolText>,
1061            d_id: AssetId<CoolText>,
1062        }
1063
1064        let (mut app, gate_opener) = create_app_with_gate(dir);
1065        app.init_asset::<CoolText>()
1066            .init_asset::<SubText>()
1067            .init_resource::<StoredEvents>()
1068            .register_asset_loader(CoolTextLoader)
1069            .add_systems(Update, store_asset_events);
1070        let asset_server = app.world().resource::<AssetServer>().clone();
1071        let handle: Handle<CoolText> = asset_server.load(a_path);
1072        let a_id = handle.id();
1073        app.update();
1074        assert_eq!(get_started_load_count(app.world()), 1);
1075
1076        {
1077            let a_text = get::<CoolText>(app.world(), a_id);
1078            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1079            assert!(a_text.is_none(), "a's asset should not exist yet");
1080            assert!(a_load.is_loading());
1081            assert!(a_deps.is_loading());
1082            assert!(a_rec_deps.is_loading());
1083        }
1084
1085        // Allow "a" to load ... wait for it to finish loading and validate results
1086        // Dependencies are still gated so they should not be loaded yet
1087        gate_opener.open(a_path);
1088        run_app_until(&mut app, |world| {
1089            let a_text = get::<CoolText>(world, a_id)?;
1090            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1091            assert_eq!(a_text.text, "a");
1092            assert_eq!(a_text.dependencies.len(), 2);
1093            assert!(a_load.is_loaded());
1094            assert!(a_deps.is_loading());
1095            assert!(a_rec_deps.is_loading());
1096
1097            let b_id = a_text.dependencies[0].id();
1098            let b_text = get::<CoolText>(world, b_id);
1099            let (b_load, b_deps, b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
1100            assert!(b_text.is_none(), "b component should not exist yet");
1101            assert!(b_load.is_loading());
1102            assert!(b_deps.is_loading());
1103            assert!(b_rec_deps.is_loading());
1104
1105            let c_id = a_text.dependencies[1].id();
1106            let c_text = get::<CoolText>(world, c_id);
1107            let (c_load, c_deps, c_rec_deps) = asset_server.get_load_states(c_id).unwrap();
1108            assert!(c_text.is_none(), "c component should not exist yet");
1109            assert!(c_load.is_loading());
1110            assert!(c_deps.is_loading());
1111            assert!(c_rec_deps.is_loading());
1112            Some(())
1113        });
1114        assert_eq!(get_started_load_count(app.world()), 3);
1115
1116        // Allow "b" to load ... wait for it to finish loading and validate results
1117        // "c" should not be loaded yet
1118        gate_opener.open(b_path);
1119        run_app_until(&mut app, |world| {
1120            let a_text = get::<CoolText>(world, a_id)?;
1121            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1122            assert_eq!(a_text.text, "a");
1123            assert_eq!(a_text.dependencies.len(), 2);
1124            assert!(a_load.is_loaded());
1125            assert!(a_deps.is_loading());
1126            assert!(a_rec_deps.is_loading());
1127
1128            let b_id = a_text.dependencies[0].id();
1129            let b_text = get::<CoolText>(world, b_id)?;
1130            let (b_load, b_deps, b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
1131            assert_eq!(b_text.text, "b");
1132            assert!(b_load.is_loaded());
1133            assert!(b_deps.is_loaded());
1134            assert!(b_rec_deps.is_loaded());
1135
1136            let c_id = a_text.dependencies[1].id();
1137            let c_text = get::<CoolText>(world, c_id);
1138            let (c_load, c_deps, c_rec_deps) = asset_server.get_load_states(c_id).unwrap();
1139            assert!(c_text.is_none(), "c component should not exist yet");
1140            assert!(c_load.is_loading());
1141            assert!(c_deps.is_loading());
1142            assert!(c_rec_deps.is_loading());
1143            Some(())
1144        });
1145        assert_eq!(get_started_load_count(app.world()), 3);
1146
1147        // Allow "c" to load ... wait for it to finish loading and validate results
1148        // all "a" dependencies should be loaded now
1149        gate_opener.open(c_path);
1150
1151        // Re-open a and b gates to allow c to load embedded deps (gates are closed after each load)
1152        gate_opener.open(a_path);
1153        gate_opener.open(b_path);
1154        run_app_until(&mut app, |world| {
1155            let a_text = get::<CoolText>(world, a_id)?;
1156            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1157            assert_eq!(a_text.text, "a");
1158            assert_eq!(a_text.embedded, "");
1159            assert_eq!(a_text.dependencies.len(), 2);
1160            assert!(a_load.is_loaded());
1161
1162            let b_id = a_text.dependencies[0].id();
1163            let b_text = get::<CoolText>(world, b_id)?;
1164            let (b_load, b_deps, b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
1165            assert_eq!(b_text.text, "b");
1166            assert_eq!(b_text.embedded, "");
1167            assert!(b_load.is_loaded());
1168            assert!(b_deps.is_loaded());
1169            assert!(b_rec_deps.is_loaded());
1170
1171            let c_id = a_text.dependencies[1].id();
1172            let c_text = get::<CoolText>(world, c_id)?;
1173            let (c_load, c_deps, c_rec_deps) = asset_server.get_load_states(c_id).unwrap();
1174            assert_eq!(c_text.text, "c");
1175            assert_eq!(c_text.embedded, "ab");
1176            assert!(c_load.is_loaded());
1177            assert!(
1178                c_deps.is_loading(),
1179                "c deps should not be loaded yet because d has not loaded"
1180            );
1181            assert!(
1182                c_rec_deps.is_loading(),
1183                "c rec deps should not be loaded yet because d has not loaded"
1184            );
1185
1186            let sub_text_id = c_text.sub_texts[0].id();
1187            let sub_text = get::<SubText>(world, sub_text_id)
1188                .expect("subtext should exist if c exists. it came from the same loader");
1189            assert_eq!(sub_text.text, "hello");
1190            let (sub_text_load, sub_text_deps, sub_text_rec_deps) =
1191                asset_server.get_load_states(sub_text_id).unwrap();
1192            assert!(sub_text_load.is_loaded());
1193            assert!(sub_text_deps.is_loaded());
1194            assert!(sub_text_rec_deps.is_loaded());
1195
1196            let d_id = c_text.dependencies[0].id();
1197            let d_text = get::<CoolText>(world, d_id);
1198            let (d_load, d_deps, d_rec_deps) = asset_server.get_load_states(d_id).unwrap();
1199            assert!(d_text.is_none(), "d component should not exist yet");
1200            assert!(d_load.is_loading());
1201            assert!(d_deps.is_loading());
1202            assert!(d_rec_deps.is_loading());
1203
1204            assert!(
1205                a_deps.is_loaded(),
1206                "If c has been loaded, the a deps should all be considered loaded"
1207            );
1208            assert!(
1209                a_rec_deps.is_loading(),
1210                "d is not loaded, so a's recursive deps should still be loading"
1211            );
1212            world.insert_resource(IdResults { b_id, c_id, d_id });
1213            Some(())
1214        });
1215        assert_eq!(get_started_load_count(app.world()), 6);
1216
1217        gate_opener.open(d_path);
1218        run_app_until(&mut app, |world| {
1219            let a_text = get::<CoolText>(world, a_id)?;
1220            let (_a_load, _a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1221            let c_id = a_text.dependencies[1].id();
1222            let c_text = get::<CoolText>(world, c_id)?;
1223            let (c_load, c_deps, c_rec_deps) = asset_server.get_load_states(c_id).unwrap();
1224            assert_eq!(c_text.text, "c");
1225            assert_eq!(c_text.embedded, "ab");
1226
1227            let d_id = c_text.dependencies[0].id();
1228            let d_text = get::<CoolText>(world, d_id)?;
1229            let (d_load, d_deps, d_rec_deps) = asset_server.get_load_states(d_id).unwrap();
1230            assert_eq!(d_text.text, "d");
1231            assert_eq!(d_text.embedded, "");
1232
1233            assert!(c_load.is_loaded());
1234            assert!(c_deps.is_loaded());
1235            assert!(c_rec_deps.is_loaded());
1236
1237            assert!(d_load.is_loaded());
1238            assert!(d_deps.is_loaded());
1239            assert!(d_rec_deps.is_loaded());
1240
1241            assert!(
1242                a_rec_deps.is_loaded(),
1243                "d is loaded, so a's recursive deps should be loaded"
1244            );
1245            Some(())
1246        });
1247
1248        assert_eq!(get_started_load_count(app.world()), 6);
1249
1250        {
1251            let mut texts = app.world_mut().resource_mut::<Assets<CoolText>>();
1252            let mut a = texts.get_mut(a_id).unwrap();
1253            a.text = "Changed".to_string();
1254        }
1255
1256        drop(handle);
1257
1258        app.update();
1259        assert_eq!(
1260            app.world().resource::<Assets<CoolText>>().len(),
1261            0,
1262            "CoolText asset entities should be despawned when no more handles exist"
1263        );
1264        app.update();
1265        // this requires a second update because the parent asset was freed in the previous app.update()
1266        assert_eq!(
1267            app.world().resource::<Assets<SubText>>().len(),
1268            0,
1269            "SubText asset entities should be despawned when no more handles exist"
1270        );
1271        let events = app.world_mut().remove_resource::<StoredEvents>().unwrap();
1272        let id_results = app.world_mut().remove_resource::<IdResults>().unwrap();
1273        let expected_events = vec![
1274            AssetEvent::Added { id: a_id },
1275            AssetEvent::LoadedWithDependencies {
1276                id: id_results.b_id,
1277            },
1278            AssetEvent::Added {
1279                id: id_results.b_id,
1280            },
1281            AssetEvent::Added {
1282                id: id_results.c_id,
1283            },
1284            AssetEvent::LoadedWithDependencies {
1285                id: id_results.d_id,
1286            },
1287            AssetEvent::LoadedWithDependencies {
1288                id: id_results.c_id,
1289            },
1290            AssetEvent::LoadedWithDependencies { id: a_id },
1291            AssetEvent::Added {
1292                id: id_results.d_id,
1293            },
1294            AssetEvent::Modified { id: a_id },
1295            AssetEvent::Unused { id: a_id },
1296            AssetEvent::Removed { id: a_id },
1297            AssetEvent::Unused {
1298                id: id_results.b_id,
1299            },
1300            AssetEvent::Removed {
1301                id: id_results.b_id,
1302            },
1303            AssetEvent::Unused {
1304                id: id_results.c_id,
1305            },
1306            AssetEvent::Removed {
1307                id: id_results.c_id,
1308            },
1309            AssetEvent::Unused {
1310                id: id_results.d_id,
1311            },
1312            AssetEvent::Removed {
1313                id: id_results.d_id,
1314            },
1315        ];
1316        assert_eq!(events.0, expected_events);
1317    }
1318
1319    #[test]
1320    fn failure_load_states() {
1321        let dir = Dir::default();
1322
1323        let a_path = "a.cool.ron";
1324        let a_ron = r#"
1325(
1326    text: "a",
1327    dependencies: [
1328        "b.cool.ron",
1329        "c.cool.ron",
1330    ],
1331    embedded_dependencies: [],
1332    sub_texts: []
1333)"#;
1334        let b_path = "b.cool.ron";
1335        let b_ron = r#"
1336(
1337    text: "b",
1338    dependencies: [],
1339    embedded_dependencies: [],
1340    sub_texts: []
1341)"#;
1342
1343        let c_path = "c.cool.ron";
1344        let c_ron = r#"
1345(
1346    text: "c",
1347    dependencies: [
1348        "d.cool.ron",
1349    ],
1350    embedded_dependencies: [],
1351    sub_texts: []
1352)"#;
1353
1354        let d_path = "d.cool.ron";
1355        let d_ron = r#"
1356(
1357    text: "d",
1358    dependencies: [],
1359    OH NO THIS ASSET IS MALFORMED
1360    embedded_dependencies: [],
1361    sub_texts: []
1362)"#;
1363
1364        dir.insert_asset_text(Path::new(a_path), a_ron);
1365        dir.insert_asset_text(Path::new(b_path), b_ron);
1366        dir.insert_asset_text(Path::new(c_path), c_ron);
1367        dir.insert_asset_text(Path::new(d_path), d_ron);
1368
1369        let (mut app, gate_opener) = create_app_with_gate(dir);
1370        app.init_asset::<CoolText>()
1371            .register_asset_loader(CoolTextLoader);
1372        let asset_server = app.world().resource::<AssetServer>().clone();
1373        let handle: Handle<CoolText> = asset_server.load(a_path);
1374        let a_id = handle.id();
1375
1376        app.update();
1377        assert_eq!(get_started_load_count(app.world()), 1);
1378        {
1379            let other_handle: Handle<CoolText> = asset_server.load(a_path);
1380            assert_eq!(
1381                other_handle, handle,
1382                "handles from consecutive load calls should be equal"
1383            );
1384            assert_eq!(
1385                other_handle.id(),
1386                handle.id(),
1387                "handle ids from consecutive load calls should be equal"
1388            );
1389
1390            app.update();
1391            // Only one load still!
1392            assert_eq!(get_started_load_count(app.world()), 1);
1393        }
1394
1395        gate_opener.open(a_path);
1396        gate_opener.open(b_path);
1397        gate_opener.open(c_path);
1398        gate_opener.open(d_path);
1399
1400        run_app_until(&mut app, |world| {
1401            let a_text = get::<CoolText>(world, a_id)?;
1402            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1403
1404            let b_id = a_text.dependencies[0].id();
1405            let b_text = get::<CoolText>(world, b_id)?;
1406            let (b_load, b_deps, b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
1407
1408            let c_id = a_text.dependencies[1].id();
1409            let c_text = get::<CoolText>(world, c_id)?;
1410            let (c_load, c_deps, c_rec_deps) = asset_server.get_load_states(c_id).unwrap();
1411
1412            let d_id = c_text.dependencies[0].id();
1413            let d_text = get::<CoolText>(world, d_id);
1414            let (d_load, d_deps, d_rec_deps) = asset_server.get_load_states(d_id).unwrap();
1415
1416            if !d_load.is_failed() {
1417                // wait until d has exited the loading state
1418                return None;
1419            }
1420
1421            assert!(d_text.is_none());
1422            assert!(d_load.is_failed());
1423            assert!(d_deps.is_failed());
1424            assert!(d_rec_deps.is_failed());
1425
1426            assert_eq!(a_text.text, "a");
1427            assert!(a_load.is_loaded());
1428            assert!(a_deps.is_loaded());
1429            assert!(a_rec_deps.is_failed());
1430
1431            assert_eq!(b_text.text, "b");
1432            assert!(b_load.is_loaded());
1433            assert!(b_deps.is_loaded());
1434            assert!(b_rec_deps.is_loaded());
1435
1436            assert_eq!(c_text.text, "c");
1437            assert!(c_load.is_loaded());
1438            assert!(c_deps.is_failed());
1439            assert!(c_rec_deps.is_failed());
1440
1441            assert!(asset_server.load_state(a_id).is_loaded());
1442            assert!(asset_server.dependency_load_state(a_id).is_loaded());
1443            assert!(asset_server
1444                .recursive_dependency_load_state(a_id)
1445                .is_failed());
1446
1447            assert!(asset_server.is_loaded(a_id));
1448            assert!(asset_server.is_loaded_with_direct_dependencies(a_id));
1449            assert!(!asset_server.is_loaded_with_dependencies(a_id));
1450
1451            Some(())
1452        });
1453
1454        assert_eq!(get_started_load_count(app.world()), 4);
1455    }
1456
1457    #[test]
1458    fn dependency_load_states() {
1459        let a_path = "a.cool.ron";
1460        let a_ron = r#"
1461(
1462    text: "a",
1463    dependencies: [
1464        "b.cool.ron",
1465        "c.cool.ron",
1466    ],
1467    embedded_dependencies: [],
1468    sub_texts: []
1469)"#;
1470        let b_path = "b.cool.ron";
1471        let b_ron = r#"
1472(
1473    text: "b",
1474    dependencies: [],
1475    MALFORMED
1476    embedded_dependencies: [],
1477    sub_texts: []
1478)"#;
1479
1480        let c_path = "c.cool.ron";
1481        let c_ron = r#"
1482(
1483    text: "c",
1484    dependencies: [],
1485    embedded_dependencies: [],
1486    sub_texts: []
1487)"#;
1488
1489        let dir = Dir::default();
1490        dir.insert_asset_text(Path::new(a_path), a_ron);
1491        dir.insert_asset_text(Path::new(b_path), b_ron);
1492        dir.insert_asset_text(Path::new(c_path), c_ron);
1493
1494        let (mut app, gate_opener) = create_app_with_gate(dir);
1495        app.init_asset::<CoolText>()
1496            .register_asset_loader(CoolTextLoader);
1497        let asset_server = app.world().resource::<AssetServer>().clone();
1498        let handle: Handle<CoolText> = asset_server.load(a_path);
1499        let a_id = handle.id();
1500
1501        app.update();
1502        assert_eq!(get_started_load_count(app.world()), 1);
1503
1504        gate_opener.open(a_path);
1505        run_app_until(&mut app, |world| {
1506            let _a_text = get::<CoolText>(world, a_id)?;
1507            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1508            assert!(a_load.is_loaded());
1509            assert!(a_deps.is_loading());
1510            assert!(a_rec_deps.is_loading());
1511            Some(())
1512        });
1513
1514        assert_eq!(get_started_load_count(app.world()), 3);
1515
1516        gate_opener.open(b_path);
1517        run_app_until(&mut app, |world| {
1518            let a_text = get::<CoolText>(world, a_id)?;
1519            let b_id = a_text.dependencies[0].id();
1520
1521            let (b_load, _b_deps, _b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
1522            if !b_load.is_failed() {
1523                // wait until b fails
1524                return None;
1525            }
1526
1527            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1528            assert!(a_load.is_loaded());
1529            assert!(a_deps.is_failed());
1530            assert!(a_rec_deps.is_failed());
1531            Some(())
1532        });
1533
1534        assert_eq!(get_started_load_count(app.world()), 3);
1535
1536        gate_opener.open(c_path);
1537        run_app_until(&mut app, |world| {
1538            let a_text = get::<CoolText>(world, a_id)?;
1539            let c_id = a_text.dependencies[1].id();
1540            // wait until c loads
1541            let _c_text = get::<CoolText>(world, c_id)?;
1542
1543            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1544            assert!(a_load.is_loaded());
1545            assert!(
1546                a_deps.is_failed(),
1547                "Successful dependency load should not overwrite a previous failure"
1548            );
1549            assert!(
1550                a_rec_deps.is_failed(),
1551                "Successful dependency load should not overwrite a previous failure"
1552            );
1553            Some(())
1554        });
1555
1556        assert_eq!(get_started_load_count(app.world()), 3);
1557    }
1558
1559    const SIMPLE_TEXT: &str = r#"
1560(
1561    text: "dep",
1562    dependencies: [],
1563    embedded_dependencies: [],
1564    sub_texts: [],
1565)"#;
1566    #[test]
1567    fn keep_gotten_strong_handles() {
1568        let dir = Dir::default();
1569        dir.insert_asset_text(Path::new("dep.cool.ron"), SIMPLE_TEXT);
1570
1571        let (mut app, _) = create_app_with_gate(dir);
1572        app.init_asset::<CoolText>()
1573            .init_asset::<SubText>()
1574            .init_resource::<StoredEvents>()
1575            .register_asset_loader(CoolTextLoader)
1576            .add_systems(Update, store_asset_events);
1577
1578        let id = {
1579            let handle = {
1580                let mut texts = app.world_mut().resource_mut::<Assets<CoolText>>();
1581                let handle = texts.add(CoolText::default());
1582                texts.get_strong_handle(handle.id()).unwrap()
1583            };
1584
1585            app.update();
1586
1587            {
1588                let text = app.world().resource::<Assets<CoolText>>().get(&handle);
1589                assert!(text.is_some());
1590            }
1591            handle.id()
1592        };
1593        // handle is dropped
1594        app.update();
1595        assert!(
1596            app.world().resource::<Assets<CoolText>>().get(id).is_none(),
1597            "asset has no handles, so it should have been dropped last update"
1598        );
1599    }
1600
1601    #[test]
1602    fn manual_asset_management() {
1603        let dir = Dir::default();
1604        let dep_path = "dep.cool.ron";
1605
1606        dir.insert_asset_text(Path::new(dep_path), SIMPLE_TEXT);
1607
1608        let (mut app, gate_opener) = create_app_with_gate(dir);
1609        app.init_asset::<CoolText>()
1610            .init_asset::<SubText>()
1611            .init_resource::<StoredEvents>()
1612            .register_asset_loader(CoolTextLoader)
1613            .add_systems(Update, store_asset_events);
1614
1615        let hello = "hello".to_string();
1616        let empty = "".to_string();
1617
1618        let id = {
1619            let handle = {
1620                let mut texts = app.world_mut().resource_mut::<Assets<CoolText>>();
1621                texts.add(CoolText {
1622                    text: hello.clone(),
1623                    embedded: empty.clone(),
1624                    dependencies: vec![],
1625                    sub_texts: Vec::new(),
1626                })
1627            };
1628
1629            app.update();
1630
1631            {
1632                let text = app
1633                    .world()
1634                    .resource::<Assets<CoolText>>()
1635                    .get(&handle)
1636                    .unwrap();
1637                assert_eq!(text.text, hello);
1638            }
1639            handle.id()
1640        };
1641        // handle is dropped
1642        app.update();
1643        assert!(
1644            app.world().resource::<Assets<CoolText>>().get(id).is_none(),
1645            "asset has no handles, so it should have been dropped last update"
1646        );
1647        // remove event is emitted
1648        app.update();
1649        let events = core::mem::take(&mut app.world_mut().resource_mut::<StoredEvents>().0);
1650        let expected_events = vec![
1651            AssetEvent::Added { id },
1652            AssetEvent::Unused { id },
1653            AssetEvent::Removed { id },
1654        ];
1655
1656        // No loads have occurred yet.
1657        assert_eq!(get_started_load_count(app.world()), 0);
1658
1659        assert_eq!(events, expected_events);
1660
1661        let dep_handle = app.world().resource::<AssetServer>().load(dep_path);
1662
1663        app.update();
1664        assert_eq!(get_started_load_count(app.world()), 1);
1665
1666        let a = CoolText {
1667            text: "a".to_string(),
1668            embedded: empty,
1669            // this dependency is behind a manual load gate, which should prevent 'a' from emitting a LoadedWithDependencies event
1670            dependencies: vec![dep_handle.clone()],
1671            sub_texts: Vec::new(),
1672        };
1673        let a_handle = app.world().resource::<AssetServer>().load_asset(a);
1674
1675        // load_asset does not count as a load.
1676        assert_eq!(get_started_load_count(app.world()), 1);
1677
1678        app.update();
1679        // TODO: ideally it doesn't take two updates for the added event to emit
1680        app.update();
1681
1682        let events = core::mem::take(&mut app.world_mut().resource_mut::<StoredEvents>().0);
1683        let expected_events = vec![AssetEvent::Added { id: a_handle.id() }];
1684        assert_eq!(events, expected_events);
1685
1686        gate_opener.open(dep_path);
1687        loop {
1688            app.update();
1689            let events = core::mem::take(&mut app.world_mut().resource_mut::<StoredEvents>().0);
1690            if events.is_empty() {
1691                continue;
1692            }
1693            let expected_events = vec![
1694                AssetEvent::LoadedWithDependencies {
1695                    id: dep_handle.id(),
1696                },
1697                AssetEvent::LoadedWithDependencies { id: a_handle.id() },
1698            ];
1699            assert_eq!(events, expected_events);
1700            break;
1701        }
1702
1703        assert_eq!(get_started_load_count(app.world()), 1);
1704
1705        app.update();
1706        let events = core::mem::take(&mut app.world_mut().resource_mut::<StoredEvents>().0);
1707        let expected_events = vec![AssetEvent::Added {
1708            id: dep_handle.id(),
1709        }];
1710        assert_eq!(events, expected_events);
1711    }
1712
1713    #[test]
1714    fn load_folder() {
1715        let dir = Dir::default();
1716
1717        let a_path = "text/a.cool.ron";
1718        let a_ron = r#"
1719(
1720    text: "a",
1721    dependencies: [
1722        "b.cool.ron",
1723    ],
1724    embedded_dependencies: [],
1725    sub_texts: [],
1726)"#;
1727        let b_path = "b.cool.ron";
1728        let b_ron = r#"
1729(
1730    text: "b",
1731    dependencies: [],
1732    embedded_dependencies: [],
1733    sub_texts: [],
1734)"#;
1735
1736        let c_path = "text/c.cool.ron";
1737        let c_ron = r#"
1738(
1739    text: "c",
1740    dependencies: [
1741    ],
1742    embedded_dependencies: [],
1743    sub_texts: [],
1744)"#;
1745        dir.insert_asset_text(Path::new(a_path), a_ron);
1746        dir.insert_asset_text(Path::new(b_path), b_ron);
1747        dir.insert_asset_text(Path::new(c_path), c_ron);
1748
1749        let (mut app, gate_opener) = create_app_with_gate(dir);
1750        app.init_asset::<CoolText>()
1751            .init_asset::<SubText>()
1752            .register_asset_loader(CoolTextLoader);
1753        let asset_server = app.world().resource::<AssetServer>().clone();
1754        let handle: Handle<LoadedFolder> = asset_server.load_folder("text");
1755
1756        // The folder started loading. The task will also try to start loading the first asset in
1757        // the folder. With the multi_threaded feature this check is racing with the first load, so
1758        // allow 1 or 2 load tasks to start.
1759        app.update();
1760        let started_load_tasks = get_started_load_count(app.world());
1761        assert!((1..=2).contains(&started_load_tasks));
1762
1763        gate_opener.open(a_path);
1764        gate_opener.open(b_path);
1765        gate_opener.open(c_path);
1766
1767        let mut cursor = MessageCursor::default();
1768        run_app_until(&mut app, |world| {
1769            let events = world.resource::<Messages<AssetEvent<LoadedFolder>>>();
1770            let asset_server = world.resource::<AssetServer>();
1771            let loaded_folders = world.resource::<Assets<LoadedFolder>>();
1772            let cool_texts = world.resource::<Assets<CoolText>>();
1773            for event in cursor.read(events) {
1774                if let AssetEvent::LoadedWithDependencies { id } = event
1775                    && *id == handle.id()
1776                {
1777                    let loaded_folder = loaded_folders.get(&handle).unwrap();
1778                    let a_handle: Handle<CoolText> =
1779                        asset_server.get_handle("text/a.cool.ron").unwrap();
1780                    let c_handle: Handle<CoolText> =
1781                        asset_server.get_handle("text/c.cool.ron").unwrap();
1782
1783                    let mut found_a = false;
1784                    let mut found_c = false;
1785                    for asset_handle in &loaded_folder.handles {
1786                        if asset_handle.id() == a_handle.id().untyped() {
1787                            found_a = true;
1788                        } else if asset_handle.id() == c_handle.id().untyped() {
1789                            found_c = true;
1790                        }
1791                    }
1792                    assert!(found_a);
1793                    assert!(found_c);
1794                    assert_eq!(loaded_folder.handles.len(), 2);
1795
1796                    let a_text = cool_texts.get(&a_handle).unwrap();
1797                    let b_text = cool_texts.get(&a_text.dependencies[0]).unwrap();
1798                    let c_text = cool_texts.get(&c_handle).unwrap();
1799
1800                    assert_eq!("a", a_text.text);
1801                    assert_eq!("b", b_text.text);
1802                    assert_eq!("c", c_text.text);
1803
1804                    return Some(());
1805                }
1806            }
1807            None
1808        });
1809        assert_eq!(get_started_load_count(app.world()), 4);
1810    }
1811
1812    /// Tests that `AssetLoadFailedEvent<A>` events are emitted and can be used to retry failed assets.
1813    #[test]
1814    fn load_error_events() {
1815        #[derive(Resource, Default)]
1816        struct ErrorTracker {
1817            tick: u64,
1818            failures: usize,
1819            queued_retries: Vec<(AssetPath<'static>, AssetId<CoolText>, u64)>,
1820            finished_asset: Option<AssetId<CoolText>>,
1821        }
1822
1823        fn asset_event_handler(
1824            mut events: MessageReader<AssetEvent<CoolText>>,
1825            mut tracker: ResMut<ErrorTracker>,
1826        ) {
1827            for event in events.read() {
1828                if let AssetEvent::LoadedWithDependencies { id } = event {
1829                    tracker.finished_asset = Some(*id);
1830                }
1831            }
1832        }
1833
1834        fn asset_load_error_event_handler(
1835            server: Res<AssetServer>,
1836            mut errors: MessageReader<AssetLoadFailedEvent<CoolText>>,
1837            mut tracker: ResMut<ErrorTracker>,
1838        ) {
1839            // In the real world, this would refer to time (not ticks)
1840            tracker.tick += 1;
1841
1842            // Retry loading past failed items
1843            let now = tracker.tick;
1844            tracker
1845                .queued_retries
1846                .retain(|(path, old_id, retry_after)| {
1847                    if now > *retry_after {
1848                        let new_handle = server.load::<CoolText>(path);
1849                        assert_eq!(&new_handle.id(), old_id);
1850                        false
1851                    } else {
1852                        true
1853                    }
1854                });
1855
1856            // Check what just failed
1857            for error in errors.read() {
1858                let (load_state, _, _) = server.get_load_states(error.id).unwrap();
1859                assert!(load_state.is_failed());
1860                assert_eq!(*error.path.source(), AssetSourceId::Name("unstable".into()));
1861                match &error.error {
1862                    AssetLoadError::AssetReaderError(read_error) => match read_error {
1863                        AssetReaderError::Io(_) => {
1864                            tracker.failures += 1;
1865                            if tracker.failures <= 2 {
1866                                // Retry in 10 ticks
1867                                tracker.queued_retries.push((
1868                                    error.path.clone(),
1869                                    error.id,
1870                                    now + 10,
1871                                ));
1872                            } else {
1873                                panic!(
1874                                    "Unexpected failure #{} (expected only 2)",
1875                                    tracker.failures
1876                                );
1877                            }
1878                        }
1879                        _ => panic!("Unexpected error type {}", read_error),
1880                    },
1881                    _ => panic!("Unexpected error type {}", error.error),
1882                }
1883            }
1884        }
1885
1886        let a_path = "text/a.cool.ron";
1887        let a_ron = r#"
1888(
1889    text: "a",
1890    dependencies: [],
1891    embedded_dependencies: [],
1892    sub_texts: [],
1893)"#;
1894
1895        let dir = Dir::default();
1896        dir.insert_asset_text(Path::new(a_path), a_ron);
1897        let unstable_reader = UnstableMemoryAssetReader::new(dir, 2);
1898
1899        let mut app = App::new();
1900        app.register_asset_source(
1901            AssetSourceId::Default,
1902            AssetSourceBuilder::new(move || {
1903                // This reader is unused, but we set it here so we don't accidentally use the
1904                // filesystem.
1905                Box::new(MemoryAssetReader {
1906                    root: Dir::default(),
1907                })
1908            }),
1909        )
1910        .register_asset_source(
1911            "unstable",
1912            AssetSourceBuilder::new(move || Box::new(unstable_reader.clone())),
1913        )
1914        .add_plugins((
1915            TaskPoolPlugin::default(),
1916            AssetPlugin {
1917                watch_for_changes_override: Some(false),
1918                use_asset_processor_override: Some(false),
1919                ..Default::default()
1920            },
1921        ))
1922        .init_asset::<CoolText>()
1923        .register_asset_loader(CoolTextLoader)
1924        .init_resource::<ErrorTracker>()
1925        .add_systems(
1926            Update,
1927            (asset_event_handler, asset_load_error_event_handler).chain(),
1928        );
1929
1930        let asset_server = app.world().resource::<AssetServer>().clone();
1931        let a_path = format!("unstable://{a_path}");
1932        let a_handle: Handle<CoolText> = asset_server.load(a_path);
1933        let a_id = a_handle.id();
1934
1935        run_app_until(&mut app, |world| {
1936            let tracker = world.resource::<ErrorTracker>();
1937            match tracker.finished_asset {
1938                Some(asset_id) => {
1939                    assert_eq!(asset_id, a_id);
1940                    let assets = world.resource::<Assets<CoolText>>();
1941                    let result = assets.get(asset_id).unwrap();
1942                    assert_eq!(result.text, "a");
1943                    Some(())
1944                }
1945                None => None,
1946            }
1947        });
1948    }
1949
1950    #[test]
1951    fn ignore_system_ambiguities_on_assets() {
1952        let mut app = create_app().0;
1953        app.init_asset::<CoolText>();
1954
1955        fn uses_assets(_asset: ResMut<Assets<CoolText>>) {}
1956        app.add_systems(Update, (uses_assets, uses_assets));
1957        app.edit_schedule(Update, |s| {
1958            s.set_build_settings(ScheduleBuildSettings {
1959                ambiguity_detection: LogLevel::Error,
1960                ..Default::default()
1961            });
1962        });
1963
1964        // running schedule does not error on ambiguity between the 2 uses_assets systems
1965        app.world_mut().run_schedule(Update);
1966    }
1967
1968    // This test is not checking a requirement, but documenting a current limitation. We simply are
1969    // not capable of loading subassets when doing nested immediate loads.
1970    #[test]
1971    fn error_on_nested_immediate_load_of_subasset() {
1972        let (mut app, dir) = create_app();
1973        dir.insert_asset_text(
1974            Path::new("a.cool.ron"),
1975            r#"(
1976    text: "b",
1977    dependencies: [],
1978    embedded_dependencies: [],
1979    sub_texts: ["A"],
1980)"#,
1981        );
1982        dir.insert_asset_text(Path::new("empty.txt"), "");
1983
1984        app.init_asset::<CoolText>()
1985            .init_asset::<SubText>()
1986            .register_asset_loader(CoolTextLoader);
1987
1988        #[derive(TypePath)]
1989        struct NestedLoadOfSubassetLoader;
1990
1991        impl AssetLoader for NestedLoadOfSubassetLoader {
1992            type Asset = TestAsset;
1993            type Error = crate::loader::LoadDirectError;
1994            type Settings = ();
1995
1996            async fn load(
1997                &self,
1998                _: &mut dyn Reader,
1999                _: &Self::Settings,
2000                load_context: &mut LoadContext<'_>,
2001            ) -> Result<Self::Asset, Self::Error> {
2002                // We expect this load to fail.
2003                load_context
2004                    .load_builder()
2005                    .load_value::<SubText>("a.cool.ron#A")
2006                    .await?;
2007                Ok(TestAsset)
2008            }
2009
2010            fn extensions(&self) -> &[&str] {
2011                &["txt"]
2012            }
2013        }
2014
2015        app.init_asset::<TestAsset>()
2016            .register_asset_loader(NestedLoadOfSubassetLoader);
2017
2018        let asset_server = app.world().resource::<AssetServer>().clone();
2019        let handle = asset_server.load::<TestAsset>("empty.txt");
2020
2021        run_app_until(&mut app, |_world| match asset_server.load_state(&handle) {
2022            LoadState::Loading => None,
2023            LoadState::Failed(err) => {
2024                let error_message = format!("{err}");
2025                assert!(error_message.contains("Requested to load an asset path (a.cool.ron#A) with a subasset, but this is unsupported"), "what? \"{error_message}\"");
2026                Some(())
2027            }
2028            state => panic!("Unexpected asset state: {state:?}"),
2029        });
2030    }
2031
2032    // validate the Asset derive macro for various asset types
2033    #[derive(Asset, TypePath)]
2034    pub struct TestAsset;
2035
2036    // Test that `VisitAssetDependencies` can be derived without deriving
2037    // `Asset`, and that the type can be used as a `#[dependency]` within an
2038    // asset.
2039    #[derive(VisitAssetDependencies)]
2040    pub struct TestNonAssetType(#[dependency] Handle<TestAsset>);
2041
2042    #[derive(Asset, TypePath)]
2043    #[expect(
2044        dead_code,
2045        reason = "This exists to ensure that `#[derive(Asset)]` works on enums. The inner variants are known not to be used."
2046    )]
2047    pub enum EnumTestAsset {
2048        Unnamed(#[dependency] Handle<TestAsset>),
2049        Named {
2050            #[dependency]
2051            handle: Handle<TestAsset>,
2052            #[dependency]
2053            vec_handles: Vec<Handle<TestAsset>>,
2054            #[dependency]
2055            embedded: TestAsset,
2056            #[dependency]
2057            set_handles: HashSet<Handle<TestAsset>>,
2058            #[dependency]
2059            untyped_set_handles: HashSet<UntypedHandle>,
2060            #[dependency]
2061            map_handles: HashMap<String, Handle<TestAsset>>,
2062            #[dependency]
2063            untyped_map_handles: HashMap<String, UntypedHandle>,
2064            #[dependency]
2065            non_asset_type: TestNonAssetType,
2066        },
2067        StructStyle(#[dependency] TestAsset),
2068        Empty,
2069    }
2070
2071    #[expect(
2072        dead_code,
2073        reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed."
2074    )]
2075    #[derive(Asset, TypePath)]
2076    pub struct StructTestAsset {
2077        #[dependency]
2078        handle: Handle<TestAsset>,
2079        #[dependency]
2080        embedded: TestAsset,
2081        #[dependency]
2082        array_handles: [Handle<TestAsset>; 5],
2083        #[dependency]
2084        untyped_array_handles: [UntypedHandle; 5],
2085        #[dependency]
2086        set_handles: HashSet<Handle<TestAsset>>,
2087        #[dependency]
2088        untyped_set_handles: HashSet<UntypedHandle>,
2089        #[dependency]
2090        map_handles: HashMap<String, Handle<TestAsset>>,
2091        #[dependency]
2092        untyped_map_handles: HashMap<String, UntypedHandle>,
2093        #[dependency]
2094        non_asset_type: TestNonAssetType,
2095    }
2096
2097    #[expect(
2098        dead_code,
2099        reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed."
2100    )]
2101    #[derive(Asset, TypePath)]
2102    pub struct TupleTestAsset(#[dependency] Handle<TestAsset>);
2103
2104    fn unapproved_path_setup(mode: UnapprovedPathMode) -> App {
2105        let dir = Dir::default();
2106        let a_path = "../a.cool.ron";
2107        let a_ron = r#"
2108(
2109    text: "a",
2110    dependencies: [],
2111    embedded_dependencies: [],
2112    sub_texts: [],
2113)"#;
2114
2115        dir.insert_asset_text(Path::new(a_path), a_ron);
2116
2117        let mut app = App::new();
2118        let memory_reader = MemoryAssetReader { root: dir };
2119        app.register_asset_source(
2120            AssetSourceId::Default,
2121            AssetSourceBuilder::new(move || Box::new(memory_reader.clone())),
2122        )
2123        .add_plugins((
2124            TaskPoolPlugin::default(),
2125            AssetPlugin {
2126                unapproved_path_mode: mode,
2127                watch_for_changes_override: Some(false),
2128                use_asset_processor_override: Some(false),
2129                ..Default::default()
2130            },
2131        ));
2132        app.init_asset::<CoolText>()
2133            .register_asset_loader(CoolTextLoader);
2134
2135        app
2136    }
2137
2138    #[test]
2139    fn unapproved_path_forbid_does_not_load_even_with_override() {
2140        let app = unapproved_path_setup(UnapprovedPathMode::Forbid);
2141
2142        let asset_server = app.world().resource::<AssetServer>().clone();
2143        assert_eq!(
2144            asset_server
2145                .load_builder()
2146                .override_unapproved()
2147                .load::<CoolText>("../a.cool.ron"),
2148            Handle::default()
2149        );
2150    }
2151
2152    #[test]
2153    fn unapproved_path_deny_does_not_load() {
2154        let app = unapproved_path_setup(UnapprovedPathMode::Deny);
2155
2156        let asset_server = app.world().resource::<AssetServer>().clone();
2157        assert_eq!(
2158            asset_server.load::<CoolText>("../a.cool.ron"),
2159            Handle::default()
2160        );
2161    }
2162
2163    #[test]
2164    fn unapproved_path_deny_loads_with_override() {
2165        let mut app = unapproved_path_setup(UnapprovedPathMode::Deny);
2166
2167        let asset_server = app.world().resource::<AssetServer>().clone();
2168        let handle = asset_server
2169            .load_builder()
2170            .override_unapproved()
2171            .load::<CoolText>("../a.cool.ron");
2172        assert_ne!(handle, Handle::default());
2173
2174        // Make sure this asset actually loads.
2175        run_app_until(&mut app, |_| asset_server.is_loaded(&handle).then_some(()));
2176    }
2177
2178    #[test]
2179    fn unapproved_path_allow_loads() {
2180        let mut app = unapproved_path_setup(UnapprovedPathMode::Allow);
2181
2182        let asset_server = app.world().resource::<AssetServer>().clone();
2183        let handle = asset_server.load::<CoolText>("../a.cool.ron");
2184        assert_ne!(handle, Handle::default());
2185
2186        // Make sure this asset actually loads.
2187        run_app_until(&mut app, |_| asset_server.is_loaded(&handle).then_some(()));
2188    }
2189
2190    #[test]
2191    fn insert_dropped_handle_returns_error() {
2192        let mut app = create_app().0;
2193
2194        app.init_asset::<TestAsset>();
2195
2196        let handle = app.world().resource::<Assets<TestAsset>>().reserve_handle();
2197        // We still have the asset ID, but we've dropped the handle so the asset is no longer live.
2198        let asset_id = handle.id();
2199        drop(handle);
2200
2201        // Allow `Assets` to detect the dropped handle.
2202        app.world_mut()
2203            .run_system_cached(Assets::<TestAsset>::track_assets)
2204            .unwrap();
2205
2206        let AssetId::Index { index, .. } = asset_id else {
2207            unreachable!("Reserving a handle always produces an index");
2208        };
2209
2210        // Try to insert an asset into the dropped handle's spot. This should not panic.
2211        assert_eq!(
2212            app.world_mut()
2213                .resource_mut::<Assets<TestAsset>>()
2214                .insert(asset_id, TestAsset),
2215            Err(InvalidGenerationError::Removed { index })
2216        );
2217    }
2218
2219    /// A loader that notifies a sender when the loader has started, and blocks on a receiver to
2220    /// simulate a long asset loader.
2221    // Note: we can't just use the GatedReader, since currently we hold the handle until after
2222    // we've selected the reader. The GatedReader blocks this process, so we need to wait until
2223    // we gate in the loader instead.
2224    #[derive(TypePath)]
2225    struct GatedLoader {
2226        in_loader_sender: Sender<()>,
2227        gate_receiver: Receiver<()>,
2228    }
2229
2230    impl AssetLoader for GatedLoader {
2231        type Asset = TestAsset;
2232        type Error = std::io::Error;
2233        type Settings = ();
2234
2235        async fn load(
2236            &self,
2237            _reader: &mut dyn Reader,
2238            _settings: &Self::Settings,
2239            _load_context: &mut LoadContext<'_>,
2240        ) -> Result<Self::Asset, Self::Error> {
2241            self.in_loader_sender.send_blocking(()).unwrap();
2242            let _ = self.gate_receiver.recv().await;
2243            Ok(TestAsset)
2244        }
2245
2246        fn extensions(&self) -> &[&str] {
2247            &["ron"]
2248        }
2249    }
2250
2251    #[test]
2252    fn dropping_handle_while_loading_cancels_load() {
2253        let (mut app, dir) = create_app();
2254
2255        let (in_loader_sender, in_loader_receiver) = async_channel::bounded(1);
2256        let (gate_sender, gate_receiver) = async_channel::bounded(1);
2257
2258        app.init_asset::<TestAsset>()
2259            .register_asset_loader(GatedLoader {
2260                in_loader_sender,
2261                gate_receiver,
2262            });
2263
2264        let path = Path::new("abc.ron");
2265        dir.insert_asset_text(path, "blah");
2266
2267        let asset_server = app.world().resource::<AssetServer>().clone();
2268
2269        // Start loading the asset. This load will get blocked by the gate.
2270        let handle = asset_server.load::<TestAsset>(path);
2271        assert!(asset_server.get_load_state(&handle).unwrap().is_loading());
2272        app.update();
2273
2274        // Make sure we are inside the loader before continuing.
2275        in_loader_receiver.recv_blocking().unwrap();
2276
2277        let asset_id = handle.id();
2278        // Dropping the handle and doing another update should result in the load being cancelled.
2279        drop(handle);
2280        app.update();
2281        assert!(asset_server.get_load_state(asset_id).is_none());
2282
2283        // Unblock the loader and then update a few times, showing that the asset never loads.
2284        gate_sender.send_blocking(()).unwrap();
2285        for _ in 0..10 {
2286            app.update();
2287            for message in app
2288                .world()
2289                .resource::<Messages<AssetEvent<TestAsset>>>()
2290                .iter_current_update_messages()
2291            {
2292                match message {
2293                    AssetEvent::Unused { .. } => {}
2294                    message => panic!("No asset events are allowed: {message:?}"),
2295                }
2296            }
2297        }
2298    }
2299
2300    #[test]
2301    fn dropping_subasset_handle_while_loading_cancels_load() {
2302        let (mut app, dir) = create_app();
2303
2304        let (in_loader_sender, in_loader_receiver) = async_channel::bounded(1);
2305        let (gate_sender, gate_receiver) = async_channel::bounded(1);
2306
2307        app.init_asset::<TestAsset>()
2308            .register_asset_loader(GatedLoader {
2309                in_loader_sender,
2310                gate_receiver,
2311            });
2312
2313        let path = Path::new("abc.ron");
2314        dir.insert_asset_text(path, "blah");
2315
2316        let asset_server = app.world().resource::<AssetServer>().clone();
2317
2318        // Start loading the subasset. This load will get blocked by the gate.
2319        // Note: it doesn't matter that the subasset doesn't actually end up existing, since the
2320        // asset system doesn't know that until after the load completes, which we cancel anyway.
2321        let handle = asset_server.load::<TestAsset>("abc.ron#sub");
2322        assert!(asset_server.get_load_state(&handle).unwrap().is_loading());
2323        app.update();
2324
2325        // Make sure we are inside the loader before continuing.
2326        in_loader_receiver.recv_blocking().unwrap();
2327
2328        let asset_id = handle.id();
2329        // Dropping the handle and doing another update should result in the load being cancelled.
2330        drop(handle);
2331        app.update();
2332        assert!(asset_server.get_load_state(asset_id).is_none());
2333
2334        // Unblock the loader and then update a few times, showing that the asset never loads.
2335        gate_sender.send_blocking(()).unwrap();
2336        for _ in 0..10 {
2337            app.update();
2338            for message in app
2339                .world()
2340                .resource::<Messages<AssetEvent<TestAsset>>>()
2341                .iter_current_update_messages()
2342            {
2343                match message {
2344                    AssetEvent::Unused { .. } => {}
2345                    message => panic!("No asset events are allowed: {message:?}"),
2346                }
2347            }
2348        }
2349    }
2350
2351    // Creates a basic app with the default asset source engineered to get back the asset event
2352    // sender.
2353    fn create_app_with_source_event_sender() -> (App, Dir, Sender<AssetSourceEvent>) {
2354        let mut app = App::new();
2355        let dir = Dir::default();
2356        let memory_reader = MemoryAssetReader { root: dir.clone() };
2357
2358        // Create a channel to pass the source event sender back to us.
2359        let (sender_sender, sender_receiver) = crossbeam_channel::bounded(1);
2360
2361        struct FakeWatcher;
2362        impl AssetWatcher for FakeWatcher {}
2363
2364        app.register_asset_source(
2365            AssetSourceId::Default,
2366            AssetSourceBuilder::new(move || Box::new(memory_reader.clone())).with_watcher(
2367                move |sender| {
2368                    sender_sender.send(sender).unwrap();
2369                    Some(Box::new(FakeWatcher))
2370                },
2371            ),
2372        )
2373        .add_plugins((
2374            TaskPoolPlugin::default(),
2375            AssetPlugin {
2376                watch_for_changes_override: Some(true),
2377                use_asset_processor_override: Some(false),
2378                ..Default::default()
2379            },
2380        ));
2381
2382        let sender = sender_receiver.try_recv().unwrap();
2383
2384        (app, dir, sender)
2385    }
2386
2387    fn collect_asset_events<A: Asset>(world: &mut World) -> Vec<AssetEvent<A>> {
2388        world
2389            .resource_mut::<Messages<AssetEvent<A>>>()
2390            .drain()
2391            .collect()
2392    }
2393
2394    fn collect_asset_load_failed_events<A: Asset>(
2395        world: &mut World,
2396    ) -> Vec<AssetLoadFailedEvent<A>> {
2397        world
2398            .resource_mut::<Messages<AssetLoadFailedEvent<A>>>()
2399            .drain()
2400            .collect()
2401    }
2402
2403    #[test]
2404    fn reloads_asset_after_source_event() {
2405        let (mut app, dir, source_events) = create_app_with_source_event_sender();
2406        let asset_server = app.world().resource::<AssetServer>().clone();
2407
2408        dir.insert_asset_text(
2409            Path::new("abc.cool.ron"),
2410            r#"(
2411    text: "a",
2412    dependencies: [],
2413    embedded_dependencies: [],
2414    sub_texts: [],
2415)"#,
2416        );
2417
2418        app.init_asset::<CoolText>()
2419            .init_asset::<SubText>()
2420            .register_asset_loader(CoolTextLoader);
2421
2422        let handle: Handle<CoolText> = asset_server.load("abc.cool.ron");
2423        run_app_until(&mut app, |world| {
2424            let messages = collect_asset_events(world);
2425            if messages.is_empty() {
2426                return None;
2427            }
2428            assert_eq!(
2429                messages,
2430                [
2431                    AssetEvent::LoadedWithDependencies { id: handle.id() },
2432                    AssetEvent::Added { id: handle.id() },
2433                ]
2434            );
2435            Some(())
2436        });
2437
2438        // Sending an asset event should result in the asset being reloaded - resulting in a
2439        // "Modified" message.
2440        source_events
2441            .send_blocking(AssetSourceEvent::ModifiedAsset(PathBuf::from(
2442                "abc.cool.ron",
2443            )))
2444            .unwrap();
2445
2446        run_app_until(&mut app, |world| {
2447            let messages = collect_asset_events(world);
2448            if messages.is_empty() {
2449                return None;
2450            }
2451            assert_eq!(
2452                messages,
2453                [
2454                    AssetEvent::LoadedWithDependencies { id: handle.id() },
2455                    AssetEvent::Modified { id: handle.id() }
2456                ]
2457            );
2458            Some(())
2459        });
2460    }
2461
2462    #[test]
2463    fn added_asset_reloads_previously_missing_asset() {
2464        let (mut app, dir, source_events) = create_app_with_source_event_sender();
2465        let asset_server = app.world().resource::<AssetServer>().clone();
2466
2467        app.init_asset::<CoolText>()
2468            .init_asset::<SubText>()
2469            .register_asset_loader(CoolTextLoader);
2470
2471        let handle: Handle<CoolText> = asset_server.load("abc.cool.ron");
2472        run_app_until(&mut app, |world| {
2473            let failed_ids = collect_asset_load_failed_events(world)
2474                .drain(..)
2475                .map(|event| event.id)
2476                .collect::<Vec<_>>();
2477            if failed_ids.is_empty() {
2478                return None;
2479            }
2480            assert_eq!(failed_ids, [handle.id()]);
2481            Some(())
2482        });
2483
2484        // The asset has already been considered as failed to load. Now we add the asset data, and
2485        // send an AddedAsset event.
2486        dir.insert_asset_text(
2487            Path::new("abc.cool.ron"),
2488            r#"(
2489    text: "a",
2490    dependencies: [],
2491    embedded_dependencies: [],
2492    sub_texts: [],
2493)"#,
2494        );
2495        source_events
2496            .send_blocking(AssetSourceEvent::AddedAsset(PathBuf::from("abc.cool.ron")))
2497            .unwrap();
2498
2499        run_app_until(&mut app, |world| {
2500            let messages = collect_asset_events(world);
2501            if messages.is_empty() {
2502                return None;
2503            }
2504            assert_eq!(
2505                messages,
2506                [
2507                    AssetEvent::LoadedWithDependencies { id: handle.id() },
2508                    AssetEvent::Added { id: handle.id() }
2509                ]
2510            );
2511            Some(())
2512        });
2513    }
2514
2515    #[test]
2516    fn same_asset_different_settings() {
2517        // Test loading the same asset twice with different settings. This should
2518        // produce two distinct assets.
2519
2520        // First, implement an asset that's a single u8, whose value is copied from
2521        // the loader settings.
2522
2523        #[derive(Asset, TypePath)]
2524        struct U8Asset(u8);
2525
2526        #[derive(Serialize, Deserialize, Default)]
2527        struct U8LoaderSettings(u8);
2528
2529        #[derive(TypePath)]
2530        struct U8Loader;
2531
2532        impl AssetLoader for U8Loader {
2533            type Asset = U8Asset;
2534            type Settings = U8LoaderSettings;
2535            type Error = crate::loader::LoadDirectError;
2536
2537            async fn load(
2538                &self,
2539                _: &mut dyn Reader,
2540                settings: &Self::Settings,
2541                _: &mut LoadContext<'_>,
2542            ) -> Result<Self::Asset, Self::Error> {
2543                Ok(U8Asset(settings.0))
2544            }
2545
2546            fn extensions(&self) -> &[&str] {
2547                &["u8"]
2548            }
2549        }
2550
2551        // Create a test asset and setup the app.
2552
2553        let (mut app, dir) = create_app();
2554        dir.insert_asset(Path::new("test.u8"), &[]);
2555
2556        app.init_asset::<U8Asset>().register_asset_loader(U8Loader);
2557
2558        let asset_server = app.world().resource::<AssetServer>();
2559
2560        // Load the test asset twice but with different settings.
2561
2562        fn load(asset_server: &AssetServer, path: &'static str, value: u8) -> Handle<U8Asset> {
2563            asset_server
2564                .load_builder()
2565                .with_settings(move |s: &mut U8LoaderSettings| s.0 = value)
2566                .load::<U8Asset>(path)
2567        }
2568
2569        let handle_1 = load(asset_server, "test.u8", 1);
2570        let handle_2 = load(asset_server, "test.u8", 2);
2571
2572        // Handles should be different.
2573
2574        // These handles should be different, but due to
2575        // https://github.com/bevyengine/bevy/pull/21564, they are not. Once 21564 is fixed, we
2576        // should replace these expects.
2577        //
2578        // assert_ne!(handle_1, handle_2);
2579        assert_eq!(handle_1, handle_2);
2580
2581        run_app_until(&mut app, |world| {
2582            let (Some(asset_1), Some(asset_2)) = (
2583                world.resource::<Assets<U8Asset>>().get(&handle_1),
2584                world.resource::<Assets<U8Asset>>().get(&handle_2),
2585            ) else {
2586                return None;
2587            };
2588
2589            // Values should match the settings.
2590
2591            // These values should be different, but due to
2592            // https://github.com/bevyengine/bevy/pull/21564, they are not. Once 21564 is fixed, we
2593            // should replace these expects.
2594            //
2595            // assert_eq!(asset_1.0, 1);
2596            // assert_eq!(asset_2.0, 2);
2597            assert_eq!(asset_1.0, asset_2.0);
2598
2599            Some(())
2600        });
2601    }
2602
2603    #[test]
2604    fn loading_two_subassets_does_not_start_two_loads() {
2605        let (mut app, dir) = create_app();
2606        dir.insert_asset(Path::new("test.txt"), &[]);
2607
2608        #[derive(TypePath)]
2609        struct TwoSubassetLoader;
2610
2611        impl AssetLoader for TwoSubassetLoader {
2612            type Asset = TestAsset;
2613            type Settings = ();
2614            type Error = std::io::Error;
2615
2616            async fn load(
2617                &self,
2618                _reader: &mut dyn Reader,
2619                _settings: &Self::Settings,
2620                load_context: &mut LoadContext<'_>,
2621            ) -> Result<Self::Asset, Self::Error> {
2622                load_context.add_labeled_asset("A", TestAsset);
2623                load_context.add_labeled_asset("B", TestAsset);
2624                Ok(TestAsset)
2625            }
2626
2627            fn extensions(&self) -> &[&str] {
2628                &["txt"]
2629            }
2630        }
2631
2632        app.init_asset::<TestAsset>()
2633            .register_asset_loader(TwoSubassetLoader);
2634
2635        let asset_server = app.world().resource::<AssetServer>().clone();
2636        let _subasset_1: Handle<TestAsset> = asset_server.load("test.txt#A");
2637        let _subasset_2: Handle<TestAsset> = asset_server.load("test.txt#B");
2638
2639        app.update();
2640
2641        // Due to https://github.com/bevyengine/bevy/issues/12756, this expectation fails. Once
2642        // #12756 is fixed, we should swap these asserts.
2643        //
2644        // assert_eq!(get_started_load_count(app.world()), 1);
2645        assert_eq!(get_started_load_count(app.world()), 2);
2646    }
2647
2648    /// A loader that immediately returns a [`TestAsset`].
2649    #[derive(TypePath)]
2650    struct TrivialLoader;
2651
2652    impl AssetLoader for TrivialLoader {
2653        type Asset = TestAsset;
2654        type Settings = ();
2655        type Error = std::io::Error;
2656
2657        async fn load(
2658            &self,
2659            _reader: &mut dyn Reader,
2660            _settings: &Self::Settings,
2661            _load_context: &mut LoadContext<'_>,
2662        ) -> Result<Self::Asset, Self::Error> {
2663            Ok(TestAsset)
2664        }
2665
2666        fn extensions(&self) -> &[&str] {
2667            &["txt"]
2668        }
2669    }
2670
2671    #[test]
2672    fn get_strong_handle_prevents_reload_when_asset_still_alive() {
2673        let (mut app, dir) = create_app();
2674        dir.insert_asset(Path::new("test.txt"), &[]);
2675
2676        app.init_asset::<TestAsset>()
2677            .register_asset_loader(TrivialLoader);
2678
2679        let asset_server = app.world().resource::<AssetServer>().clone();
2680        let original_handle: Handle<TestAsset> = asset_server.load("test.txt");
2681
2682        // Wait for the asset to load.
2683        run_app_until(&mut app, |world| {
2684            world
2685                .resource::<Assets<TestAsset>>()
2686                .get(&original_handle)
2687                .map(|_| ())
2688        });
2689
2690        assert_eq!(get_started_load_count(app.world()), 1);
2691
2692        // Get a new strong handle from the original handle's ID.
2693        let new_handle = app
2694            .world_mut()
2695            .resource_mut::<Assets<TestAsset>>()
2696            .get_strong_handle(original_handle.id())
2697            .unwrap();
2698
2699        // Drop the original handle. This should still leave the asset alive.
2700        drop(original_handle);
2701
2702        app.update();
2703        assert!(app
2704            .world()
2705            .resource::<Assets<TestAsset>>()
2706            .get(&new_handle)
2707            .is_some());
2708
2709        let _other_handle: Handle<TestAsset> = asset_server.load("test.txt");
2710        app.update();
2711        // The asset server should **not** have started a new load, since the asset is still alive.
2712
2713        // Due to https://github.com/bevyengine/bevy/issues/20651, we do get a second load. Once
2714        // #20651 is fixed, we should swap these asserts.
2715        //
2716        // assert_eq!(get_started_load_count(app.world()), 1);
2717        assert_eq!(get_started_load_count(app.world()), 2);
2718    }
2719
2720    #[test]
2721    fn immediate_nested_asset_loads_dependency() {
2722        let (mut app, dir) = create_app();
2723
2724        /// This asset holds a handle to its dependency.
2725        #[derive(Asset, TypePath)]
2726        struct DeferredNested(Handle<TestAsset>);
2727
2728        #[derive(TypePath)]
2729        struct DeferredNestedLoader;
2730
2731        impl AssetLoader for DeferredNestedLoader {
2732            type Asset = DeferredNested;
2733            type Settings = ();
2734            type Error = std::io::Error;
2735
2736            async fn load(
2737                &self,
2738                reader: &mut dyn Reader,
2739                _: &Self::Settings,
2740                load_context: &mut LoadContext<'_>,
2741            ) -> Result<Self::Asset, Self::Error> {
2742                let mut nested_path = String::new();
2743                reader.read_to_string(&mut nested_path).await?;
2744                Ok(DeferredNested(load_context.load(nested_path)))
2745            }
2746
2747            fn extensions(&self) -> &[&str] {
2748                &["defer"]
2749            }
2750        }
2751
2752        /// This asset holds a handle a dependency of one of its dependencies.
2753        #[derive(Asset, TypePath)]
2754        struct ImmediateNested(Handle<TestAsset>);
2755
2756        #[derive(TypePath)]
2757        struct ImmediateNestedLoader;
2758
2759        impl AssetLoader for ImmediateNestedLoader {
2760            type Asset = ImmediateNested;
2761            type Settings = ();
2762            type Error = std::io::Error;
2763
2764            async fn load(
2765                &self,
2766                reader: &mut dyn Reader,
2767                _: &Self::Settings,
2768                load_context: &mut LoadContext<'_>,
2769            ) -> Result<Self::Asset, Self::Error> {
2770                let mut nested_path = String::new();
2771                reader.read_to_string(&mut nested_path).await?;
2772                let deferred_nested: LoadedAsset<DeferredNested> = load_context
2773                    .load_builder()
2774                    .load_value(nested_path)
2775                    .await
2776                    .unwrap();
2777                Ok(ImmediateNested(deferred_nested.get().0.clone()))
2778            }
2779
2780            fn extensions(&self) -> &[&str] {
2781                &["immediate"]
2782            }
2783        }
2784
2785        app.init_asset::<TestAsset>()
2786            .init_asset::<DeferredNested>()
2787            .init_asset::<ImmediateNested>()
2788            .register_asset_loader(TrivialLoader)
2789            .register_asset_loader(DeferredNestedLoader)
2790            .register_asset_loader(ImmediateNestedLoader);
2791
2792        dir.insert_asset_text(Path::new("a.immediate"), "b.defer");
2793        dir.insert_asset_text(Path::new("b.defer"), "c.txt");
2794        dir.insert_asset_text(Path::new("c.txt"), "hiya");
2795
2796        let server = app.world().resource::<AssetServer>().clone();
2797        let immediate_handle: Handle<ImmediateNested> = server.load("a.immediate");
2798
2799        run_app_until(&mut app, |world| {
2800            let immediate_assets = world.resource::<Assets<ImmediateNested>>();
2801            let immediate = immediate_assets.get(&immediate_handle)?;
2802
2803            let test_asset_handle = immediate.0.clone();
2804            world
2805                .resource::<Assets<TestAsset>>()
2806                .get(&test_asset_handle)?;
2807
2808            // The immediate asset is loaded, and the asset it got from its immediate load is also
2809            // loaded.
2810            Some(())
2811        });
2812    }
2813
2814    pub(crate) fn read_asset_as_string(dir: &Dir, path: &Path) -> String {
2815        let bytes = dir.get_asset(path).unwrap();
2816        str::from_utf8(bytes.value()).unwrap().to_string()
2817    }
2818
2819    pub(crate) fn read_meta_as_string(dir: &Dir, path: &Path) -> String {
2820        let bytes = dir.get_metadata(path).unwrap();
2821        str::from_utf8(bytes.value()).unwrap().to_string()
2822    }
2823
2824    #[test]
2825    fn writes_default_meta_for_loader() {
2826        let (mut app, source) = create_app();
2827
2828        app.register_asset_loader(CoolTextLoader);
2829
2830        const ASSET_PATH: &str = "abc.cool.ron";
2831        source.insert_asset_text(Path::new(ASSET_PATH), "blah");
2832
2833        let asset_server = app.world().resource::<AssetServer>().clone();
2834        block_on(asset_server.write_default_loader_meta_file_for_path(ASSET_PATH)).unwrap();
2835
2836        assert_eq!(
2837            read_meta_as_string(&source, Path::new(ASSET_PATH)),
2838            r#"(
2839    meta_format_version: "1.0",
2840    asset: Load(
2841        loader: "bevy_asset::tests::CoolTextLoader",
2842        settings: (),
2843    ),
2844)"#
2845        );
2846    }
2847
2848    #[test]
2849    fn write_default_meta_does_not_overwrite() {
2850        let (mut app, source) = create_app();
2851
2852        app.register_asset_loader(CoolTextLoader);
2853
2854        const ASSET_PATH: &str = "abc.cool.ron";
2855        source.insert_asset_text(Path::new(ASSET_PATH), "blah");
2856        const META_TEXT: &str = "hey i'm walkin here!";
2857        source.insert_meta_text(Path::new(ASSET_PATH), META_TEXT);
2858
2859        let asset_server = app.world().resource::<AssetServer>().clone();
2860        assert!(matches!(
2861            block_on(asset_server.write_default_loader_meta_file_for_path(ASSET_PATH)),
2862            Err(WriteDefaultMetaError::MetaAlreadyExists)
2863        ));
2864
2865        assert_eq!(
2866            read_meta_as_string(&source, Path::new(ASSET_PATH)),
2867            META_TEXT
2868        );
2869    }
2870
2871    #[test]
2872    fn asset_dependency_is_tracked_when_not_loaded() {
2873        let (mut app, dir) = create_app();
2874
2875        #[derive(Asset, TypePath)]
2876        struct AssetWithDep {
2877            #[dependency]
2878            dep: Handle<TestAsset>,
2879        }
2880
2881        #[derive(TypePath)]
2882        struct AssetWithDepLoader;
2883
2884        impl AssetLoader for AssetWithDepLoader {
2885            type Asset = TestAsset;
2886            type Settings = ();
2887            type Error = std::io::Error;
2888
2889            async fn load(
2890                &self,
2891                _reader: &mut dyn Reader,
2892                _settings: &Self::Settings,
2893                load_context: &mut LoadContext<'_>,
2894            ) -> Result<Self::Asset, Self::Error> {
2895                // Load the asset in the root context, but then put the handle in the subasset. So
2896                // the subasset's (internal) load context never loaded `dep`.
2897                let dep = load_context.load::<TestAsset>("abc.ron");
2898                load_context.add_labeled_asset("subasset", AssetWithDep { dep });
2899                Ok(TestAsset)
2900            }
2901
2902            fn extensions(&self) -> &[&str] {
2903                &["with_deps"]
2904            }
2905        }
2906
2907        // Write some data so the loaders have something to load (even though they don't use the
2908        // data).
2909        dir.insert_asset_text(Path::new("abc.ron"), "");
2910        dir.insert_asset_text(Path::new("blah.with_deps"), "");
2911
2912        let (in_loader_sender, in_loader_receiver) = async_channel::bounded(1);
2913        let (gate_sender, gate_receiver) = async_channel::bounded(1);
2914        app.init_asset::<TestAsset>()
2915            .init_asset::<AssetWithDep>()
2916            .register_asset_loader(GatedLoader {
2917                in_loader_sender,
2918                gate_receiver,
2919            })
2920            .register_asset_loader(AssetWithDepLoader);
2921
2922        let asset_server = app.world().resource::<AssetServer>().clone();
2923        let subasset_handle: Handle<AssetWithDep> = asset_server.load("blah.with_deps#subasset");
2924
2925        run_app_until(&mut app, |_| {
2926            asset_server.is_loaded(&subasset_handle).then_some(())
2927        });
2928        // Even though the subasset is loaded, and its load context never loaded its dependency, it
2929        // still depends on its dependency, so that is tracked correctly here.
2930        assert!(!asset_server.is_loaded_with_dependencies(&subasset_handle));
2931
2932        let dep_handle: Handle<TestAsset> = app
2933            .world()
2934            .resource::<Assets<AssetWithDep>>()
2935            .get(&subasset_handle)
2936            .unwrap()
2937            .dep
2938            .clone();
2939
2940        // Pass the gate in the dependency loader.
2941        in_loader_receiver.recv_blocking().unwrap();
2942        gate_sender.send_blocking(()).unwrap();
2943
2944        run_app_until(&mut app, |_| {
2945            asset_server.is_loaded(&dep_handle).then_some(())
2946        });
2947        // Now that the dependency is loaded, the subasset is counted as loaded with dependencies!
2948        assert!(asset_server.is_loaded_with_dependencies(&subasset_handle));
2949    }
2950
2951    // A simplified version of `LoadState` for easier comparison.
2952    #[derive(Debug, PartialEq, Eq)]
2953    enum TestLoadState {
2954        NotLoaded,
2955        Loading,
2956        Loaded,
2957        Failed(TestAssetLoadError),
2958    }
2959
2960    // A simplified subset of `AssetLoadError` for easier comparison.
2961    #[derive(Debug, PartialEq, Eq)]
2962    enum TestAssetLoadError {
2963        RequestedHandleTypeMismatch {
2964            requested: TypeId,
2965            actual_asset_name: &'static str,
2966        },
2967        MissingAssetLoader,
2968        AssetReaderErrorNotFound,
2969        AssetLoaderError,
2970        MissingLabel,
2971    }
2972
2973    impl From<LoadState> for TestLoadState {
2974        fn from(value: LoadState) -> Self {
2975            match value {
2976                LoadState::NotLoaded => Self::NotLoaded,
2977                LoadState::Loading => Self::Loading,
2978                LoadState::Loaded => Self::Loaded,
2979                LoadState::Failed(err) => Self::Failed((&*err).into()),
2980            }
2981        }
2982    }
2983
2984    impl From<&AssetLoadError> for TestAssetLoadError {
2985        fn from(value: &AssetLoadError) -> TestAssetLoadError {
2986            match value {
2987                AssetLoadError::RequestedHandleTypeMismatch {
2988                    requested,
2989                    actual_asset_name,
2990                    ..
2991                } => Self::RequestedHandleTypeMismatch {
2992                    requested: *requested,
2993                    actual_asset_name,
2994                },
2995                AssetLoadError::MissingAssetLoader { .. } => Self::MissingAssetLoader,
2996                AssetLoadError::AssetReaderError(AssetReaderError::NotFound(_)) => {
2997                    Self::AssetReaderErrorNotFound
2998                }
2999                AssetLoadError::AssetLoaderError { .. } => Self::AssetLoaderError,
3000                AssetLoadError::MissingLabel { .. } => Self::MissingLabel,
3001                _ => panic!("TestAssetLoadError's From<&AssetLoaderError> is missing a case for AssetLoadError \"{:?}\".", value),
3002            }
3003        }
3004    }
3005
3006    // An asset type that doesn't have a registered loader.
3007    #[derive(Asset, TypePath)]
3008    struct LoaderlessAsset;
3009
3010    // Load the given path and test that `AssetServer::get_load_state` returns
3011    // the given state.
3012    fn test_load_state<A: Asset>(
3013        label: &'static str,
3014        path: &'static str,
3015        expected_load_state: TestLoadState,
3016    ) {
3017        let (mut app, dir) = create_app();
3018
3019        app.init_asset::<CoolText>()
3020            .init_asset::<SubText>()
3021            .init_asset::<LoaderlessAsset>()
3022            .register_asset_loader(CoolTextLoader);
3023
3024        dir.insert_asset_text(
3025            Path::new("test.cool.ron"),
3026            r#"
3027(
3028    text: "test",
3029    dependencies: [],
3030    embedded_dependencies: [],
3031    sub_texts: ["subasset"],
3032)"#,
3033        );
3034
3035        dir.insert_asset_text(Path::new("malformed.cool.ron"), "MALFORMED");
3036
3037        let asset_server = app.world().resource::<AssetServer>().clone();
3038        let handle = asset_server.load::<A>(path);
3039        let mut load_state = TestLoadState::NotLoaded;
3040
3041        for _ in 0..LARGE_ITERATION_COUNT {
3042            app.update();
3043            load_state = asset_server.get_load_state(&handle).unwrap().into();
3044            if load_state == expected_load_state {
3045                break;
3046            }
3047        }
3048
3049        assert!(
3050            load_state == expected_load_state,
3051            "For test \"{}\", expected {:?} but got {:?}.",
3052            label,
3053            expected_load_state,
3054            load_state,
3055        );
3056    }
3057
3058    // Tests that `AssetServer::get_load_state` returns the correct state after
3059    // various loads, some of which trigger errors.
3060    #[test]
3061    fn load_failure() {
3062        test_load_state::<CoolText>("root asset exists", "test.cool.ron", TestLoadState::Loaded);
3063
3064        test_load_state::<SubText>(
3065            "sub-asset exists",
3066            "test.cool.ron#subasset",
3067            TestLoadState::Loaded,
3068        );
3069
3070        test_load_state::<CoolText>(
3071            "root asset does not exist",
3072            "does_not_exist.cool.ron",
3073            TestLoadState::Failed(TestAssetLoadError::AssetReaderErrorNotFound),
3074        );
3075
3076        test_load_state::<CoolText>(
3077            "sub-asset of root asset that does not exist",
3078            "does_not_exist.cool.ron#subasset",
3079            TestLoadState::Failed(TestAssetLoadError::AssetReaderErrorNotFound),
3080        );
3081
3082        test_load_state::<SubText>(
3083            "sub-asset does not exist",
3084            "test.cool.ron#does_not_exist",
3085            TestLoadState::Failed(TestAssetLoadError::MissingLabel),
3086        );
3087
3088        test_load_state::<CoolText>(
3089            "sub-asset is not requested type",
3090            "test.cool.ron#subasset",
3091            TestLoadState::Failed(TestAssetLoadError::RequestedHandleTypeMismatch {
3092                requested: TypeId::of::<CoolText>(),
3093                actual_asset_name: "bevy_asset::tests::SubText",
3094            }),
3095        );
3096
3097        test_load_state::<CoolText>(
3098            "malformed root asset",
3099            "malformed.cool.ron",
3100            TestLoadState::Failed(TestAssetLoadError::AssetLoaderError),
3101        );
3102
3103        test_load_state::<CoolText>(
3104            "sub-asset of malformed root asset",
3105            "malformed.cool.ron#subasset",
3106            TestLoadState::Failed(TestAssetLoadError::AssetLoaderError),
3107        );
3108
3109        test_load_state::<LoaderlessAsset>(
3110            "root asset has no loader",
3111            "loaderless",
3112            TestLoadState::Failed(TestAssetLoadError::MissingAssetLoader),
3113        );
3114    }
3115
3116    #[test]
3117    fn load_empty_path_returns_default() {
3118        let mut app = create_app().0;
3119
3120        // Not necessary but better to make things more realistic to ensure we hit the right error
3121        // case.
3122        app.init_asset::<TestAsset>()
3123            .register_asset_loader(TrivialLoader);
3124
3125        const TYPE_ID: TypeId = TypeId::of::<TestAsset>();
3126
3127        fn boring_settings(_: &mut ()) {}
3128
3129        let asset_server = app.world().resource::<AssetServer>().clone();
3130
3131        for path in ["", "no_path://#WithALabel"] {
3132            // TODO: We have way too many "load" variants. We **need** to simplify this.
3133            assert_eq!(asset_server.load(path), Handle::<TestAsset>::default());
3134            assert_eq!(
3135                asset_server.load_builder().with_guard(()).load(path),
3136                Handle::<TestAsset>::default()
3137            );
3138            assert_eq!(
3139                asset_server
3140                    .load_builder()
3141                    .with_guard(())
3142                    .override_unapproved()
3143                    .load(path),
3144                Handle::<TestAsset>::default()
3145            );
3146            assert_eq!(
3147                asset_server
3148                    .load_builder()
3149                    .with_guard(())
3150                    .with_settings(boring_settings)
3151                    .load(path),
3152                Handle::<TestAsset>::default()
3153            );
3154            assert_eq!(
3155                asset_server.load_builder().load_erased(TYPE_ID, path),
3156                Handle::<TestAsset>::default()
3157            );
3158            assert_eq!(
3159                asset_server.load_builder().override_unapproved().load(path),
3160                Handle::<TestAsset>::default()
3161            );
3162            assert_eq!(
3163                asset_server.load_builder().load_untyped(path),
3164                Handle::default()
3165            );
3166            assert!(matches!(
3167                block_on(asset_server.load_builder().load_untyped_async(path)),
3168                Err(AssetLoadError::EmptyPath(reported_path)) if AssetPath::from(path) == reported_path
3169            ));
3170            assert_eq!(
3171                asset_server
3172                    .load_builder()
3173                    .with_settings(|_: &mut ()| {})
3174                    .load(path),
3175                Handle::<TestAsset>::default()
3176            );
3177            assert_eq!(
3178                asset_server
3179                    .load_builder()
3180                    .with_settings(|_: &mut ()| {})
3181                    .override_unapproved()
3182                    .load(path),
3183                Handle::<TestAsset>::default()
3184            );
3185        }
3186    }
3187
3188    #[test]
3189    fn resource_are_dependencies_loaded() {
3190        let (mut app, dir) = create_app();
3191        dir.insert_asset_text(Path::new("abc.txt"), "");
3192        dir.insert_asset_text(Path::new("def.txt"), "");
3193        dir.insert_asset_text(Path::new("ghi.txt"), "");
3194
3195        app.init_asset::<TestAsset>()
3196            .register_asset_loader(TrivialLoader);
3197
3198        let asset_server = app.world().resource::<AssetServer>().clone();
3199
3200        #[derive(Resource, VisitAssetDependencies)]
3201        struct MyAssetHolder {
3202            #[dependency]
3203            abc: Handle<TestAsset>,
3204            #[dependency]
3205            def: Handle<TestAsset>,
3206            #[dependency]
3207            ghi: Handle<TestAsset>,
3208        }
3209
3210        app.insert_resource(MyAssetHolder {
3211            abc: asset_server.load("abc.txt"),
3212            def: asset_server.load("def.txt"),
3213            ghi: asset_server.load("ghi.txt"),
3214        });
3215
3216        assert!(!asset_server.are_dependencies_loaded(app.world().resource::<MyAssetHolder>()));
3217        assert!(
3218            !asset_server.are_direct_dependencies_loaded(app.world().resource::<MyAssetHolder>())
3219        );
3220
3221        run_app_until(&mut app, |world| {
3222            asset_server
3223                .are_dependencies_loaded(world.resource::<MyAssetHolder>())
3224                .then_some(())
3225        });
3226        assert!(
3227            asset_server.are_direct_dependencies_loaded(app.world().resource::<MyAssetHolder>())
3228        );
3229    }
3230
3231    #[test]
3232    fn hot_reload_folder() {
3233        let (mut app, dir, event_sender) = create_app_with_source_event_sender();
3234
3235        app.init_asset::<CoolText>()
3236            .init_asset::<SubText>()
3237            .register_asset_loader(CoolTextLoader);
3238
3239        let abc_path = Path::new("dir/abc.cool.ron");
3240        let def_path = Path::new("dir/def.cool.ron");
3241        dir.insert_asset_text(abc_path, &serialize_as_cool_text("abc"));
3242        dir.insert_asset_text(def_path, &serialize_as_cool_text("def"));
3243
3244        let asset_server = app.world().resource::<AssetServer>().clone();
3245
3246        let folder_handle = asset_server.load_folder("dir");
3247        run_app_until(&mut app, |_| {
3248            asset_server
3249                .is_loaded_with_dependencies(&folder_handle)
3250                .then_some(())
3251        });
3252
3253        let folder = app
3254            .world()
3255            .resource::<Assets<LoadedFolder>>()
3256            .get(&folder_handle)
3257            .unwrap();
3258        assert_eq!(folder.handles.len(), 2);
3259        let mut handles = folder
3260            .handles
3261            .iter()
3262            .cloned()
3263            .map(UntypedHandle::typed::<CoolText>)
3264            .collect::<Vec<_>>();
3265        // Sort the handles so we know abc is first and def is second.
3266        handles.sort_by_key(|handle| handle.path().unwrap().path().to_path_buf());
3267
3268        let abc_handle = handles[0].clone();
3269        let def_handle = handles[1].clone();
3270
3271        let cool_texts = app.world().resource::<Assets<CoolText>>();
3272        assert_eq!(cool_texts.get(&abc_handle).unwrap().text, "abc");
3273        assert_eq!(cool_texts.get(&def_handle).unwrap().text, "def");
3274
3275        // Before doing any hot reloading stuff, clear out any AssetEvent messages.
3276        app.world_mut()
3277            .resource_mut::<Messages<AssetEvent<LoadedFolder>>>()
3278            .clear();
3279
3280        // Add a new asset to the folder, and send an event to trigger hot-reloading.
3281        let ghi_path = Path::new("dir/ghi.cool.ron");
3282        dir.insert_asset_text(ghi_path, &serialize_as_cool_text("ghi"));
3283        event_sender
3284            .send_blocking(AssetSourceEvent::AddedAsset(ghi_path.to_path_buf()))
3285            .unwrap();
3286
3287        run_app_until(&mut app, |world| {
3288            for event in world
3289                .resource_mut::<Messages<AssetEvent<LoadedFolder>>>()
3290                .drain()
3291            {
3292                if let AssetEvent::LoadedWithDependencies { id } = event
3293                    && id == folder_handle.id()
3294                {
3295                    return Some(());
3296                }
3297            }
3298            None
3299        });
3300
3301        let folder = app
3302            .world()
3303            .resource::<Assets<LoadedFolder>>()
3304            .get(&folder_handle)
3305            .unwrap();
3306        assert_eq!(folder.handles.len(), 3);
3307        let mut handles = folder
3308            .handles
3309            .iter()
3310            .cloned()
3311            .map(UntypedHandle::typed::<CoolText>)
3312            .collect::<Vec<_>>();
3313        // Sort the handles so we know the order is abc, def, and ghi.
3314        handles.sort_by_key(|handle| handle.path().unwrap().path().to_path_buf());
3315
3316        let new_abc_handle = handles[0].clone();
3317        let new_def_handle = handles[1].clone();
3318        let new_ghi_handle = handles[2].clone();
3319
3320        assert_eq!(new_abc_handle, abc_handle);
3321        assert_eq!(new_def_handle, def_handle);
3322
3323        let cool_texts = app.world().resource::<Assets<CoolText>>();
3324        assert_eq!(cool_texts.get(&new_abc_handle).unwrap().text, "abc");
3325        assert_eq!(cool_texts.get(&new_def_handle).unwrap().text, "def");
3326        assert_eq!(cool_texts.get(&new_ghi_handle).unwrap().text, "ghi");
3327    }
3328}