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`](bevy_ecs::prelude::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//! If you want to save your assets back to disk, you should implement [`AssetSaver`](saver::AssetSaver) as well.
139//! This trait mirrors [`AssetLoader`] in structure, and works in tandem with [`AssetWriter`](io::AssetWriter), which mirrors [`AssetReader`](io::AssetReader).
140
141// FIXME(3492): remove once docs are ready
142// FIXME(15321): solve CI failures, then replace with `#![expect()]`.
143#![allow(missing_docs, reason = "Not all docs are written yet, see #3492.")]
144#![cfg_attr(docsrs, feature(doc_auto_cfg))]
145#![doc(
146    html_logo_url = "https://bevyengine.org/assets/icon.png",
147    html_favicon_url = "https://bevyengine.org/assets/icon.png"
148)]
149
150extern crate alloc;
151
152pub mod io;
153pub mod meta;
154pub mod processor;
155pub mod saver;
156pub mod transformer;
157
158/// The asset prelude.
159///
160/// This includes the most common types in this crate, re-exported for your convenience.
161pub mod prelude {
162    #[doc(hidden)]
163    pub use crate::{
164        Asset, AssetApp, AssetEvent, AssetId, AssetMode, AssetPlugin, AssetServer, Assets,
165        DirectAssetAccessExt, Handle, UntypedHandle,
166    };
167}
168
169mod assets;
170mod direct_access_ext;
171mod event;
172mod folder;
173mod handle;
174mod id;
175mod loader;
176mod loader_builders;
177mod path;
178mod reflect;
179mod render_asset;
180mod server;
181
182pub use assets::*;
183pub use bevy_asset_macros::Asset;
184pub use direct_access_ext::DirectAssetAccessExt;
185pub use event::*;
186pub use folder::*;
187pub use futures_lite::{AsyncReadExt, AsyncWriteExt};
188pub use handle::*;
189pub use id::*;
190pub use loader::*;
191pub use loader_builders::{
192    Deferred, DynamicTyped, Immediate, NestedLoader, StaticTyped, UnknownTyped,
193};
194pub use path::*;
195pub use reflect::*;
196pub use render_asset::*;
197pub use server::*;
198
199/// Rusty Object Notation, a crate used to serialize and deserialize bevy assets.
200pub use ron;
201
202use crate::{
203    io::{embedded::EmbeddedAssetRegistry, AssetSourceBuilder, AssetSourceBuilders, AssetSourceId},
204    processor::{AssetProcessor, Process},
205};
206use alloc::sync::Arc;
207use bevy_app::{App, Last, Plugin, PreUpdate};
208use bevy_ecs::{
209    reflect::AppTypeRegistry,
210    schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet},
211    world::FromWorld,
212};
213use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath};
214use bevy_utils::{tracing::error, HashSet};
215use core::any::TypeId;
216
217#[cfg(all(feature = "file_watcher", not(feature = "multi_threaded")))]
218compile_error!(
219    "The \"file_watcher\" feature for hot reloading requires the \
220    \"multi_threaded\" feature to be functional.\n\
221    Consider either disabling the \"file_watcher\" feature or enabling \"multi_threaded\""
222);
223
224/// Provides "asset" loading and processing functionality. An [`Asset`] is a "runtime value" that is loaded from an [`AssetSource`],
225/// which can be something like a filesystem, a network, etc.
226///
227/// Supports flexible "modes", such as [`AssetMode::Processed`] and
228/// [`AssetMode::Unprocessed`] that enable using the asset workflow that best suits your project.
229///
230/// [`AssetSource`]: io::AssetSource
231pub struct AssetPlugin {
232    /// The default file path to use (relative to the project root) for unprocessed assets.
233    pub file_path: String,
234    /// The default file path to use (relative to the project root) for processed assets.
235    pub processed_file_path: String,
236    /// If set, will override the default "watch for changes" setting. By default "watch for changes" will be `false` unless
237    /// the `watch` cargo feature is set. `watch` can be enabled manually, or it will be automatically enabled if a specific watcher
238    /// like `file_watcher` is enabled.
239    ///
240    /// Most use cases should leave this set to [`None`] and enable a specific watcher feature such as `file_watcher` to enable
241    /// watching for dev-scenarios.
242    pub watch_for_changes_override: Option<bool>,
243    /// The [`AssetMode`] to use for this server.
244    pub mode: AssetMode,
245    /// How/If asset meta files should be checked.
246    pub meta_check: AssetMetaCheck,
247}
248
249/// Controls whether or not assets are pre-processed before being loaded.
250///
251/// This setting is controlled by setting [`AssetPlugin::mode`].
252///
253/// When building on web, asset preprocessing can cause problems due to the lack of filesystem access.
254/// See [bevy#10157](https://github.com/bevyengine/bevy/issues/10157) for context.
255#[derive(Debug)]
256pub enum AssetMode {
257    /// Loads assets from their [`AssetSource`]'s default [`AssetReader`] without any "preprocessing".
258    ///
259    /// [`AssetReader`]: io::AssetReader
260    /// [`AssetSource`]: io::AssetSource
261    Unprocessed,
262    /// Assets will be "pre-processed". This enables assets to be imported / converted / optimized ahead of time.
263    ///
264    /// Assets will be read from their unprocessed [`AssetSource`] (defaults to the `assets` folder),
265    /// processed according to their [`AssetMeta`], and written to their processed [`AssetSource`] (defaults to the `imported_assets/Default` folder).
266    ///
267    /// By default, this assumes the processor _has already been run_. It will load assets from their final processed [`AssetReader`].
268    ///
269    /// When developing an app, you should enable the `asset_processor` cargo feature, which will run the asset processor at startup. This should generally
270    /// be used in combination with the `file_watcher` cargo feature, which enables hot-reloading of assets that have changed. When both features are enabled,
271    /// 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.
272    ///
273    /// [`AssetMeta`]: meta::AssetMeta
274    /// [`AssetSource`]: io::AssetSource
275    /// [`AssetReader`]: io::AssetReader
276    Processed,
277}
278
279/// Configures how / if meta files will be checked. If an asset's meta file is not checked, the default meta for the asset
280/// will be used.
281#[derive(Debug, Default, Clone)]
282pub enum AssetMetaCheck {
283    /// Always check if assets have meta files. If the meta does not exist, the default meta will be used.
284    #[default]
285    Always,
286    /// Only look up meta files for the provided paths. The default meta will be used for any paths not contained in this set.
287    Paths(HashSet<AssetPath<'static>>),
288    /// 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.
289    Never,
290}
291
292impl Default for AssetPlugin {
293    fn default() -> Self {
294        Self {
295            mode: AssetMode::Unprocessed,
296            file_path: Self::DEFAULT_UNPROCESSED_FILE_PATH.to_string(),
297            processed_file_path: Self::DEFAULT_PROCESSED_FILE_PATH.to_string(),
298            watch_for_changes_override: None,
299            meta_check: AssetMetaCheck::default(),
300        }
301    }
302}
303
304impl AssetPlugin {
305    const DEFAULT_UNPROCESSED_FILE_PATH: &'static str = "assets";
306    /// NOTE: this is in the Default sub-folder to make this forward compatible with "import profiles"
307    /// and to allow us to put the "processor transaction log" at `imported_assets/log`
308    const DEFAULT_PROCESSED_FILE_PATH: &'static str = "imported_assets/Default";
309}
310
311impl Plugin for AssetPlugin {
312    fn build(&self, app: &mut App) {
313        let embedded = EmbeddedAssetRegistry::default();
314        {
315            let mut sources = app
316                .world_mut()
317                .get_resource_or_init::<AssetSourceBuilders>();
318            sources.init_default_source(
319                &self.file_path,
320                (!matches!(self.mode, AssetMode::Unprocessed))
321                    .then_some(self.processed_file_path.as_str()),
322            );
323            embedded.register_source(&mut sources);
324        }
325        {
326            let mut watch = cfg!(feature = "watch");
327            if let Some(watch_override) = self.watch_for_changes_override {
328                watch = watch_override;
329            }
330            match self.mode {
331                AssetMode::Unprocessed => {
332                    let mut builders = app.world_mut().resource_mut::<AssetSourceBuilders>();
333                    let sources = builders.build_sources(watch, false);
334
335                    app.insert_resource(AssetServer::new_with_meta_check(
336                        sources,
337                        AssetServerMode::Unprocessed,
338                        self.meta_check.clone(),
339                        watch,
340                    ));
341                }
342                AssetMode::Processed => {
343                    #[cfg(feature = "asset_processor")]
344                    {
345                        let mut builders = app.world_mut().resource_mut::<AssetSourceBuilders>();
346                        let processor = AssetProcessor::new(&mut builders);
347                        let mut sources = builders.build_sources(false, watch);
348                        sources.gate_on_processor(processor.data.clone());
349                        // the main asset server shares loaders with the processor asset server
350                        app.insert_resource(AssetServer::new_with_loaders(
351                            sources,
352                            processor.server().data.loaders.clone(),
353                            AssetServerMode::Processed,
354                            AssetMetaCheck::Always,
355                            watch,
356                        ))
357                        .insert_resource(processor)
358                        .add_systems(bevy_app::Startup, AssetProcessor::start);
359                    }
360                    #[cfg(not(feature = "asset_processor"))]
361                    {
362                        let mut builders = app.world_mut().resource_mut::<AssetSourceBuilders>();
363                        let sources = builders.build_sources(false, watch);
364                        app.insert_resource(AssetServer::new_with_meta_check(
365                            sources,
366                            AssetServerMode::Processed,
367                            AssetMetaCheck::Always,
368                            watch,
369                        ));
370                    }
371                }
372            }
373        }
374        app.insert_resource(embedded)
375            .init_asset::<LoadedFolder>()
376            .init_asset::<LoadedUntypedAsset>()
377            .init_asset::<()>()
378            .add_event::<UntypedAssetLoadFailedEvent>()
379            .configure_sets(PreUpdate, TrackAssets.after(handle_internal_asset_events))
380            // `handle_internal_asset_events` requires the use of `&mut World`,
381            // and as a result has ambiguous system ordering with all other systems in `PreUpdate`.
382            // This is virtually never a real problem: asset loading is async and so anything that interacts directly with it
383            // needs to be robust to stochastic delays anyways.
384            .add_systems(PreUpdate, handle_internal_asset_events.ambiguous_with_all())
385            .register_type::<AssetPath>();
386    }
387}
388
389/// Declares that this type is an asset,
390/// which can be loaded and managed by the [`AssetServer`] and stored in [`Assets`] collections.
391///
392/// Generally, assets are large, complex, and/or expensive to load from disk, and are often authored by artists or designers.
393///
394/// [`TypePath`] is largely used for diagnostic purposes, and should almost always be implemented by deriving [`Reflect`] on your type.
395/// [`VisitAssetDependencies`] is used to track asset dependencies, and an implementation is automatically generated when deriving [`Asset`].
396#[diagnostic::on_unimplemented(
397    message = "`{Self}` is not an `Asset`",
398    label = "invalid `Asset`",
399    note = "consider annotating `{Self}` with `#[derive(Asset)]`"
400)]
401pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + 'static {}
402
403/// This trait defines how to visit the dependencies of an asset.
404/// For example, a 3D model might require both textures and meshes to be loaded.
405///
406/// Note that this trait is automatically implemented when deriving [`Asset`].
407pub trait VisitAssetDependencies {
408    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId));
409}
410
411impl<A: Asset> VisitAssetDependencies for Handle<A> {
412    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
413        visit(self.id().untyped());
414    }
415}
416
417impl<A: Asset> VisitAssetDependencies for Option<Handle<A>> {
418    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
419        if let Some(handle) = self {
420            visit(handle.id().untyped());
421        }
422    }
423}
424
425impl VisitAssetDependencies for UntypedHandle {
426    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
427        visit(self.id());
428    }
429}
430
431impl VisitAssetDependencies for Option<UntypedHandle> {
432    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
433        if let Some(handle) = self {
434            visit(handle.id());
435        }
436    }
437}
438
439impl<A: Asset> VisitAssetDependencies for Vec<Handle<A>> {
440    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
441        for dependency in self {
442            visit(dependency.id().untyped());
443        }
444    }
445}
446
447impl VisitAssetDependencies for Vec<UntypedHandle> {
448    fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) {
449        for dependency in self {
450            visit(dependency.id());
451        }
452    }
453}
454
455/// Adds asset-related builder methods to [`App`].
456pub trait AssetApp {
457    /// Registers the given `loader` in the [`App`]'s [`AssetServer`].
458    fn register_asset_loader<L: AssetLoader>(&mut self, loader: L) -> &mut Self;
459    /// Registers the given `processor` in the [`App`]'s [`AssetProcessor`].
460    fn register_asset_processor<P: Process>(&mut self, processor: P) -> &mut Self;
461    /// Registers the given [`AssetSourceBuilder`] with the given `id`.
462    ///
463    /// Note that asset sources must be registered before adding [`AssetPlugin`] to your application,
464    /// since registered asset sources are built at that point and not after.
465    fn register_asset_source(
466        &mut self,
467        id: impl Into<AssetSourceId<'static>>,
468        source: AssetSourceBuilder,
469    ) -> &mut Self;
470    /// Sets the default asset processor for the given `extension`.
471    fn set_default_asset_processor<P: Process>(&mut self, extension: &str) -> &mut Self;
472    /// Initializes the given loader in the [`App`]'s [`AssetServer`].
473    fn init_asset_loader<L: AssetLoader + FromWorld>(&mut self) -> &mut Self;
474    /// Initializes the given [`Asset`] in the [`App`] by:
475    /// * Registering the [`Asset`] in the [`AssetServer`]
476    /// * Initializing the [`AssetEvent`] resource for the [`Asset`]
477    /// * Adding other relevant systems and resources for the [`Asset`]
478    /// * Ignoring schedule ambiguities in [`Assets`] resource. Any time a system takes
479    ///     mutable access to this resource this causes a conflict, but they rarely actually
480    ///     modify the same underlying asset.
481    fn init_asset<A: Asset>(&mut self) -> &mut Self;
482    /// Registers the asset type `T` using `[App::register]`,
483    /// and adds [`ReflectAsset`] type data to `T` and [`ReflectHandle`] type data to [`Handle<T>`] in the type registry.
484    ///
485    /// This enables reflection code to access assets. For detailed information, see the docs on [`ReflectAsset`] and [`ReflectHandle`].
486    fn register_asset_reflect<A>(&mut self) -> &mut Self
487    where
488        A: Asset + Reflect + FromReflect + GetTypeRegistration;
489    /// Preregisters a loader for the given extensions, that will block asset loads until a real loader
490    /// is registered.
491    fn preregister_asset_loader<L: AssetLoader>(&mut self, extensions: &[&str]) -> &mut Self;
492}
493
494impl AssetApp for App {
495    fn register_asset_loader<L: AssetLoader>(&mut self, loader: L) -> &mut Self {
496        self.world()
497            .resource::<AssetServer>()
498            .register_loader(loader);
499        self
500    }
501
502    fn register_asset_processor<P: Process>(&mut self, processor: P) -> &mut Self {
503        if let Some(asset_processor) = self.world().get_resource::<AssetProcessor>() {
504            asset_processor.register_processor(processor);
505        }
506        self
507    }
508
509    fn register_asset_source(
510        &mut self,
511        id: impl Into<AssetSourceId<'static>>,
512        source: AssetSourceBuilder,
513    ) -> &mut Self {
514        let id = AssetSourceId::from_static(id);
515        if self.world().get_resource::<AssetServer>().is_some() {
516            error!("{} must be registered before `AssetPlugin` (typically added as part of `DefaultPlugins`)", id);
517        }
518
519        {
520            let mut sources = self
521                .world_mut()
522                .get_resource_or_init::<AssetSourceBuilders>();
523            sources.insert(id, source);
524        }
525
526        self
527    }
528
529    fn set_default_asset_processor<P: Process>(&mut self, extension: &str) -> &mut Self {
530        if let Some(asset_processor) = self.world().get_resource::<AssetProcessor>() {
531            asset_processor.set_default_processor::<P>(extension);
532        }
533        self
534    }
535
536    fn init_asset_loader<L: AssetLoader + FromWorld>(&mut self) -> &mut Self {
537        let loader = L::from_world(self.world_mut());
538        self.register_asset_loader(loader)
539    }
540
541    fn init_asset<A: Asset>(&mut self) -> &mut Self {
542        let assets = Assets::<A>::default();
543        self.world()
544            .resource::<AssetServer>()
545            .register_asset(&assets);
546        if self.world().contains_resource::<AssetProcessor>() {
547            let processor = self.world().resource::<AssetProcessor>();
548            // The processor should have its own handle provider separate from the Asset storage
549            // to ensure the id spaces are entirely separate. Not _strictly_ necessary, but
550            // desirable.
551            processor
552                .server()
553                .register_handle_provider(AssetHandleProvider::new(
554                    TypeId::of::<A>(),
555                    Arc::new(AssetIndexAllocator::default()),
556                ));
557        }
558        self.insert_resource(assets)
559            .allow_ambiguous_resource::<Assets<A>>()
560            .add_event::<AssetEvent<A>>()
561            .add_event::<AssetLoadFailedEvent<A>>()
562            .register_type::<Handle<A>>()
563            .add_systems(
564                Last,
565                Assets::<A>::asset_events
566                    .run_if(Assets::<A>::asset_events_condition)
567                    .in_set(AssetEvents),
568            )
569            .add_systems(PreUpdate, Assets::<A>::track_assets.in_set(TrackAssets))
570    }
571
572    fn register_asset_reflect<A>(&mut self) -> &mut Self
573    where
574        A: Asset + Reflect + FromReflect + GetTypeRegistration,
575    {
576        let type_registry = self.world().resource::<AppTypeRegistry>();
577        {
578            let mut type_registry = type_registry.write();
579
580            type_registry.register::<A>();
581            type_registry.register::<Handle<A>>();
582            type_registry.register_type_data::<A, ReflectAsset>();
583            type_registry.register_type_data::<Handle<A>, ReflectHandle>();
584        }
585
586        self
587    }
588
589    fn preregister_asset_loader<L: AssetLoader>(&mut self, extensions: &[&str]) -> &mut Self {
590        self.world_mut()
591            .resource_mut::<AssetServer>()
592            .preregister_loader::<L>(extensions);
593        self
594    }
595}
596
597/// A system set that holds all "track asset" operations.
598#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
599pub struct TrackAssets;
600
601/// A system set where events accumulated in [`Assets`] are applied to the [`AssetEvent`] [`Events`] resource.
602///
603/// [`Events`]: bevy_ecs::event::Events
604#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
605pub struct AssetEvents;
606
607#[cfg(test)]
608mod tests {
609    use crate::{
610        self as bevy_asset,
611        folder::LoadedFolder,
612        handle::Handle,
613        io::{
614            gated::{GateOpener, GatedReader},
615            memory::{Dir, MemoryAssetReader},
616            AssetReader, AssetReaderError, AssetSource, AssetSourceId, Reader,
617        },
618        loader::{AssetLoader, LoadContext},
619        Asset, AssetApp, AssetEvent, AssetId, AssetLoadError, AssetLoadFailedEvent, AssetPath,
620        AssetPlugin, AssetServer, Assets,
621    };
622    use alloc::sync::Arc;
623    use bevy_app::{App, Update};
624    use bevy_core::TaskPoolPlugin;
625    use bevy_ecs::{
626        event::EventCursor,
627        prelude::*,
628        schedule::{LogLevel, ScheduleBuildSettings},
629    };
630    use bevy_log::LogPlugin;
631    use bevy_reflect::TypePath;
632    use bevy_utils::{Duration, HashMap};
633    use derive_more::derive::{Display, Error, From};
634    use serde::{Deserialize, Serialize};
635    use std::path::Path;
636
637    #[derive(Asset, TypePath, Debug, Default)]
638    pub struct CoolText {
639        pub text: String,
640        pub embedded: String,
641        #[dependency]
642        pub dependencies: Vec<Handle<CoolText>>,
643        #[dependency]
644        pub sub_texts: Vec<Handle<SubText>>,
645    }
646
647    #[derive(Asset, TypePath, Debug)]
648    pub struct SubText {
649        text: String,
650    }
651
652    #[derive(Serialize, Deserialize)]
653    pub struct CoolTextRon {
654        text: String,
655        dependencies: Vec<String>,
656        embedded_dependencies: Vec<String>,
657        sub_texts: Vec<String>,
658    }
659
660    #[derive(Default)]
661    pub struct CoolTextLoader;
662
663    #[derive(Error, Display, Debug, From)]
664    pub enum CoolTextLoaderError {
665        #[display("Could not load dependency: {dependency}")]
666        CannotLoadDependency { dependency: AssetPath<'static> },
667        #[display("A RON error occurred during loading")]
668        RonSpannedError(ron::error::SpannedError),
669        #[display("An IO error occurred during loading")]
670        Io(std::io::Error),
671    }
672
673    impl AssetLoader for CoolTextLoader {
674        type Asset = CoolText;
675
676        type Settings = ();
677
678        type Error = CoolTextLoaderError;
679
680        async fn load(
681            &self,
682            reader: &mut dyn Reader,
683            _settings: &Self::Settings,
684            load_context: &mut LoadContext<'_>,
685        ) -> Result<Self::Asset, Self::Error> {
686            let mut bytes = Vec::new();
687            reader.read_to_end(&mut bytes).await?;
688            let mut ron: CoolTextRon = ron::de::from_bytes(&bytes)?;
689            let mut embedded = String::new();
690            for dep in ron.embedded_dependencies {
691                let loaded = load_context
692                    .loader()
693                    .immediate()
694                    .load::<CoolText>(&dep)
695                    .await
696                    .map_err(|_| Self::Error::CannotLoadDependency {
697                        dependency: dep.into(),
698                    })?;
699                let cool = loaded.get();
700                embedded.push_str(&cool.text);
701            }
702            Ok(CoolText {
703                text: ron.text,
704                embedded,
705                dependencies: ron
706                    .dependencies
707                    .iter()
708                    .map(|p| load_context.load(p))
709                    .collect(),
710                sub_texts: ron
711                    .sub_texts
712                    .drain(..)
713                    .map(|text| load_context.add_labeled_asset(text.clone(), SubText { text }))
714                    .collect(),
715            })
716        }
717
718        fn extensions(&self) -> &[&str] {
719            &["cool.ron"]
720        }
721    }
722
723    /// A dummy [`CoolText`] asset reader that only succeeds after `failure_count` times it's read from for each asset.
724    #[derive(Default, Clone)]
725    pub struct UnstableMemoryAssetReader {
726        pub attempt_counters: Arc<std::sync::Mutex<HashMap<Box<Path>, usize>>>,
727        pub load_delay: Duration,
728        memory_reader: MemoryAssetReader,
729        failure_count: usize,
730    }
731
732    impl UnstableMemoryAssetReader {
733        pub fn new(root: Dir, failure_count: usize) -> Self {
734            Self {
735                load_delay: Duration::from_millis(10),
736                memory_reader: MemoryAssetReader { root },
737                attempt_counters: Default::default(),
738                failure_count,
739            }
740        }
741    }
742
743    impl AssetReader for UnstableMemoryAssetReader {
744        async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
745            self.memory_reader.is_directory(path).await
746        }
747        async fn read_directory<'a>(
748            &'a self,
749            path: &'a Path,
750        ) -> Result<Box<bevy_asset::io::PathStream>, AssetReaderError> {
751            self.memory_reader.read_directory(path).await
752        }
753        async fn read_meta<'a>(
754            &'a self,
755            path: &'a Path,
756        ) -> Result<impl Reader + 'a, AssetReaderError> {
757            self.memory_reader.read_meta(path).await
758        }
759        async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
760            let attempt_number = {
761                let mut attempt_counters = self.attempt_counters.lock().unwrap();
762                if let Some(existing) = attempt_counters.get_mut(path) {
763                    *existing += 1;
764                    *existing
765                } else {
766                    attempt_counters.insert(path.into(), 1);
767                    1
768                }
769            };
770
771            if attempt_number <= self.failure_count {
772                let io_error = std::io::Error::new(
773                    std::io::ErrorKind::ConnectionRefused,
774                    format!(
775                        "Simulated failure {attempt_number} of {}",
776                        self.failure_count
777                    ),
778                );
779                let wait = self.load_delay;
780                return async move {
781                    std::thread::sleep(wait);
782                    Err(AssetReaderError::Io(io_error.into()))
783                }
784                .await;
785            }
786
787            self.memory_reader.read(path).await
788        }
789    }
790
791    fn test_app(dir: Dir) -> (App, GateOpener) {
792        let mut app = App::new();
793        let (gated_memory_reader, gate_opener) = GatedReader::new(MemoryAssetReader { root: dir });
794        app.register_asset_source(
795            AssetSourceId::Default,
796            AssetSource::build().with_reader(move || Box::new(gated_memory_reader.clone())),
797        )
798        .add_plugins((
799            TaskPoolPlugin::default(),
800            LogPlugin::default(),
801            AssetPlugin::default(),
802        ));
803        (app, gate_opener)
804    }
805
806    pub fn run_app_until(app: &mut App, mut predicate: impl FnMut(&mut World) -> Option<()>) {
807        for _ in 0..LARGE_ITERATION_COUNT {
808            app.update();
809            if predicate(app.world_mut()).is_some() {
810                return;
811            }
812        }
813
814        panic!("Ran out of loops to return `Some` from `predicate`");
815    }
816
817    const LARGE_ITERATION_COUNT: usize = 10000;
818
819    fn get<A: Asset>(world: &World, id: AssetId<A>) -> Option<&A> {
820        world.resource::<Assets<A>>().get(id)
821    }
822
823    #[derive(Resource, Default)]
824    struct StoredEvents(Vec<AssetEvent<CoolText>>);
825
826    fn store_asset_events(
827        mut reader: EventReader<AssetEvent<CoolText>>,
828        mut storage: ResMut<StoredEvents>,
829    ) {
830        storage.0.extend(reader.read().cloned());
831    }
832
833    #[test]
834    fn load_dependencies() {
835        // The particular usage of GatedReader in this test will cause deadlocking if running single-threaded
836        #[cfg(not(feature = "multi_threaded"))]
837        panic!("This test requires the \"multi_threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi_threaded");
838
839        let dir = Dir::default();
840
841        let a_path = "a.cool.ron";
842        let a_ron = r#"
843(
844    text: "a",
845    dependencies: [
846        "foo/b.cool.ron",
847        "c.cool.ron",
848    ],
849    embedded_dependencies: [],
850    sub_texts: [],
851)"#;
852        let b_path = "foo/b.cool.ron";
853        let b_ron = r#"
854(
855    text: "b",
856    dependencies: [],
857    embedded_dependencies: [],
858    sub_texts: [],
859)"#;
860
861        let c_path = "c.cool.ron";
862        let c_ron = r#"
863(
864    text: "c",
865    dependencies: [
866        "d.cool.ron",
867    ],
868    embedded_dependencies: ["a.cool.ron", "foo/b.cool.ron"],
869    sub_texts: ["hello"],
870)"#;
871
872        let d_path = "d.cool.ron";
873        let d_ron = r#"
874(
875    text: "d",
876    dependencies: [],
877    embedded_dependencies: [],
878    sub_texts: [],
879)"#;
880
881        dir.insert_asset_text(Path::new(a_path), a_ron);
882        dir.insert_asset_text(Path::new(b_path), b_ron);
883        dir.insert_asset_text(Path::new(c_path), c_ron);
884        dir.insert_asset_text(Path::new(d_path), d_ron);
885
886        #[derive(Resource)]
887        struct IdResults {
888            b_id: AssetId<CoolText>,
889            c_id: AssetId<CoolText>,
890            d_id: AssetId<CoolText>,
891        }
892
893        let (mut app, gate_opener) = test_app(dir);
894        app.init_asset::<CoolText>()
895            .init_asset::<SubText>()
896            .init_resource::<StoredEvents>()
897            .register_asset_loader(CoolTextLoader)
898            .add_systems(Update, store_asset_events);
899        let asset_server = app.world().resource::<AssetServer>().clone();
900        let handle: Handle<CoolText> = asset_server.load(a_path);
901        let a_id = handle.id();
902        app.update();
903        {
904            let a_text = get::<CoolText>(app.world(), a_id);
905            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
906            assert!(a_text.is_none(), "a's asset should not exist yet");
907            assert!(a_load.is_loading());
908            assert!(a_deps.is_loading());
909            assert!(a_rec_deps.is_loading());
910        }
911
912        // Allow "a" to load ... wait for it to finish loading and validate results
913        // Dependencies are still gated so they should not be loaded yet
914        gate_opener.open(a_path);
915        run_app_until(&mut app, |world| {
916            let a_text = get::<CoolText>(world, a_id)?;
917            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
918            assert_eq!(a_text.text, "a");
919            assert_eq!(a_text.dependencies.len(), 2);
920            assert!(a_load.is_loaded());
921            assert!(a_deps.is_loading());
922            assert!(a_rec_deps.is_loading());
923
924            let b_id = a_text.dependencies[0].id();
925            let b_text = get::<CoolText>(world, b_id);
926            let (b_load, b_deps, b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
927            assert!(b_text.is_none(), "b component should not exist yet");
928            assert!(b_load.is_loading());
929            assert!(b_deps.is_loading());
930            assert!(b_rec_deps.is_loading());
931
932            let c_id = a_text.dependencies[1].id();
933            let c_text = get::<CoolText>(world, c_id);
934            let (c_load, c_deps, c_rec_deps) = asset_server.get_load_states(c_id).unwrap();
935            assert!(c_text.is_none(), "c component should not exist yet");
936            assert!(c_load.is_loading());
937            assert!(c_deps.is_loading());
938            assert!(c_rec_deps.is_loading());
939            Some(())
940        });
941
942        // Allow "b" to load ... wait for it to finish loading and validate results
943        // "c" should not be loaded yet
944        gate_opener.open(b_path);
945        run_app_until(&mut app, |world| {
946            let a_text = get::<CoolText>(world, a_id)?;
947            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
948            assert_eq!(a_text.text, "a");
949            assert_eq!(a_text.dependencies.len(), 2);
950            assert!(a_load.is_loaded());
951            assert!(a_deps.is_loading());
952            assert!(a_rec_deps.is_loading());
953
954            let b_id = a_text.dependencies[0].id();
955            let b_text = get::<CoolText>(world, b_id)?;
956            let (b_load, b_deps, b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
957            assert_eq!(b_text.text, "b");
958            assert!(b_load.is_loaded());
959            assert!(b_deps.is_loaded());
960            assert!(b_rec_deps.is_loaded());
961
962            let c_id = a_text.dependencies[1].id();
963            let c_text = get::<CoolText>(world, c_id);
964            let (c_load, c_deps, c_rec_deps) = asset_server.get_load_states(c_id).unwrap();
965            assert!(c_text.is_none(), "c component should not exist yet");
966            assert!(c_load.is_loading());
967            assert!(c_deps.is_loading());
968            assert!(c_rec_deps.is_loading());
969            Some(())
970        });
971
972        // Allow "c" to load ... wait for it to finish loading and validate results
973        // all "a" dependencies should be loaded now
974        gate_opener.open(c_path);
975
976        // Re-open a and b gates to allow c to load embedded deps (gates are closed after each load)
977        gate_opener.open(a_path);
978        gate_opener.open(b_path);
979        run_app_until(&mut app, |world| {
980            let a_text = get::<CoolText>(world, a_id)?;
981            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
982            assert_eq!(a_text.text, "a");
983            assert_eq!(a_text.embedded, "");
984            assert_eq!(a_text.dependencies.len(), 2);
985            assert!(a_load.is_loaded());
986
987            let b_id = a_text.dependencies[0].id();
988            let b_text = get::<CoolText>(world, b_id)?;
989            let (b_load, b_deps, b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
990            assert_eq!(b_text.text, "b");
991            assert_eq!(b_text.embedded, "");
992            assert!(b_load.is_loaded());
993            assert!(b_deps.is_loaded());
994            assert!(b_rec_deps.is_loaded());
995
996            let c_id = a_text.dependencies[1].id();
997            let c_text = get::<CoolText>(world, c_id)?;
998            let (c_load, c_deps, c_rec_deps) = asset_server.get_load_states(c_id).unwrap();
999            assert_eq!(c_text.text, "c");
1000            assert_eq!(c_text.embedded, "ab");
1001            assert!(c_load.is_loaded());
1002            assert!(
1003                c_deps.is_loading(),
1004                "c deps should not be loaded yet because d has not loaded"
1005            );
1006            assert!(
1007                c_rec_deps.is_loading(),
1008                "c rec deps should not be loaded yet because d has not loaded"
1009            );
1010
1011            let sub_text_id = c_text.sub_texts[0].id();
1012            let sub_text = get::<SubText>(world, sub_text_id)
1013                .expect("subtext should exist if c exists. it came from the same loader");
1014            assert_eq!(sub_text.text, "hello");
1015            let (sub_text_load, sub_text_deps, sub_text_rec_deps) =
1016                asset_server.get_load_states(sub_text_id).unwrap();
1017            assert!(sub_text_load.is_loaded());
1018            assert!(sub_text_deps.is_loaded());
1019            assert!(sub_text_rec_deps.is_loaded());
1020
1021            let d_id = c_text.dependencies[0].id();
1022            let d_text = get::<CoolText>(world, d_id);
1023            let (d_load, d_deps, d_rec_deps) = asset_server.get_load_states(d_id).unwrap();
1024            assert!(d_text.is_none(), "d component should not exist yet");
1025            assert!(d_load.is_loading());
1026            assert!(d_deps.is_loading());
1027            assert!(d_rec_deps.is_loading());
1028
1029            assert!(
1030                a_deps.is_loaded(),
1031                "If c has been loaded, the a deps should all be considered loaded"
1032            );
1033            assert!(
1034                a_rec_deps.is_loading(),
1035                "d is not loaded, so a's recursive deps should still be loading"
1036            );
1037            world.insert_resource(IdResults { b_id, c_id, d_id });
1038            Some(())
1039        });
1040
1041        gate_opener.open(d_path);
1042        run_app_until(&mut app, |world| {
1043            let a_text = get::<CoolText>(world, a_id)?;
1044            let (_a_load, _a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1045            let c_id = a_text.dependencies[1].id();
1046            let c_text = get::<CoolText>(world, c_id)?;
1047            let (c_load, c_deps, c_rec_deps) = asset_server.get_load_states(c_id).unwrap();
1048            assert_eq!(c_text.text, "c");
1049            assert_eq!(c_text.embedded, "ab");
1050
1051            let d_id = c_text.dependencies[0].id();
1052            let d_text = get::<CoolText>(world, d_id)?;
1053            let (d_load, d_deps, d_rec_deps) = asset_server.get_load_states(d_id).unwrap();
1054            assert_eq!(d_text.text, "d");
1055            assert_eq!(d_text.embedded, "");
1056
1057            assert!(c_load.is_loaded());
1058            assert!(c_deps.is_loaded());
1059            assert!(c_rec_deps.is_loaded());
1060
1061            assert!(d_load.is_loaded());
1062            assert!(d_deps.is_loaded());
1063            assert!(d_rec_deps.is_loaded());
1064
1065            assert!(
1066                a_rec_deps.is_loaded(),
1067                "d is loaded, so a's recursive deps should be loaded"
1068            );
1069            Some(())
1070        });
1071
1072        {
1073            let mut texts = app.world_mut().resource_mut::<Assets<CoolText>>();
1074            let a = texts.get_mut(a_id).unwrap();
1075            a.text = "Changed".to_string();
1076        }
1077
1078        drop(handle);
1079
1080        app.update();
1081        assert_eq!(
1082            app.world().resource::<Assets<CoolText>>().len(),
1083            0,
1084            "CoolText asset entities should be despawned when no more handles exist"
1085        );
1086        app.update();
1087        // this requires a second update because the parent asset was freed in the previous app.update()
1088        assert_eq!(
1089            app.world().resource::<Assets<SubText>>().len(),
1090            0,
1091            "SubText asset entities should be despawned when no more handles exist"
1092        );
1093        let events = app.world_mut().remove_resource::<StoredEvents>().unwrap();
1094        let id_results = app.world_mut().remove_resource::<IdResults>().unwrap();
1095        let expected_events = vec![
1096            AssetEvent::Added { id: a_id },
1097            AssetEvent::LoadedWithDependencies {
1098                id: id_results.b_id,
1099            },
1100            AssetEvent::Added {
1101                id: id_results.b_id,
1102            },
1103            AssetEvent::Added {
1104                id: id_results.c_id,
1105            },
1106            AssetEvent::LoadedWithDependencies {
1107                id: id_results.d_id,
1108            },
1109            AssetEvent::LoadedWithDependencies {
1110                id: id_results.c_id,
1111            },
1112            AssetEvent::LoadedWithDependencies { id: a_id },
1113            AssetEvent::Added {
1114                id: id_results.d_id,
1115            },
1116            AssetEvent::Modified { id: a_id },
1117            AssetEvent::Unused { id: a_id },
1118            AssetEvent::Removed { id: a_id },
1119            AssetEvent::Unused {
1120                id: id_results.b_id,
1121            },
1122            AssetEvent::Removed {
1123                id: id_results.b_id,
1124            },
1125            AssetEvent::Unused {
1126                id: id_results.c_id,
1127            },
1128            AssetEvent::Removed {
1129                id: id_results.c_id,
1130            },
1131            AssetEvent::Unused {
1132                id: id_results.d_id,
1133            },
1134            AssetEvent::Removed {
1135                id: id_results.d_id,
1136            },
1137        ];
1138        assert_eq!(events.0, expected_events);
1139    }
1140
1141    #[test]
1142    fn failure_load_states() {
1143        // The particular usage of GatedReader in this test will cause deadlocking if running single-threaded
1144        #[cfg(not(feature = "multi_threaded"))]
1145        panic!("This test requires the \"multi_threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi_threaded");
1146
1147        let dir = Dir::default();
1148
1149        let a_path = "a.cool.ron";
1150        let a_ron = r#"
1151(
1152    text: "a",
1153    dependencies: [
1154        "b.cool.ron",
1155        "c.cool.ron",
1156    ],
1157    embedded_dependencies: [],
1158    sub_texts: []
1159)"#;
1160        let b_path = "b.cool.ron";
1161        let b_ron = r#"
1162(
1163    text: "b",
1164    dependencies: [],
1165    embedded_dependencies: [],
1166    sub_texts: []
1167)"#;
1168
1169        let c_path = "c.cool.ron";
1170        let c_ron = r#"
1171(
1172    text: "c",
1173    dependencies: [
1174        "d.cool.ron",
1175    ],
1176    embedded_dependencies: [],
1177    sub_texts: []
1178)"#;
1179
1180        let d_path = "d.cool.ron";
1181        let d_ron = r#"
1182(
1183    text: "d",
1184    dependencies: [],
1185    OH NO THIS ASSET IS MALFORMED
1186    embedded_dependencies: [],
1187    sub_texts: []
1188)"#;
1189
1190        dir.insert_asset_text(Path::new(a_path), a_ron);
1191        dir.insert_asset_text(Path::new(b_path), b_ron);
1192        dir.insert_asset_text(Path::new(c_path), c_ron);
1193        dir.insert_asset_text(Path::new(d_path), d_ron);
1194
1195        let (mut app, gate_opener) = test_app(dir);
1196        app.init_asset::<CoolText>()
1197            .register_asset_loader(CoolTextLoader);
1198        let asset_server = app.world().resource::<AssetServer>().clone();
1199        let handle: Handle<CoolText> = asset_server.load(a_path);
1200        let a_id = handle.id();
1201        {
1202            let other_handle: Handle<CoolText> = asset_server.load(a_path);
1203            assert_eq!(
1204                other_handle, handle,
1205                "handles from consecutive load calls should be equal"
1206            );
1207            assert_eq!(
1208                other_handle.id(),
1209                handle.id(),
1210                "handle ids from consecutive load calls should be equal"
1211            );
1212        }
1213
1214        gate_opener.open(a_path);
1215        gate_opener.open(b_path);
1216        gate_opener.open(c_path);
1217        gate_opener.open(d_path);
1218
1219        run_app_until(&mut app, |world| {
1220            let a_text = get::<CoolText>(world, a_id)?;
1221            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1222
1223            let b_id = a_text.dependencies[0].id();
1224            let b_text = get::<CoolText>(world, b_id)?;
1225            let (b_load, b_deps, b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
1226
1227            let c_id = a_text.dependencies[1].id();
1228            let c_text = get::<CoolText>(world, c_id)?;
1229            let (c_load, c_deps, c_rec_deps) = asset_server.get_load_states(c_id).unwrap();
1230
1231            let d_id = c_text.dependencies[0].id();
1232            let d_text = get::<CoolText>(world, d_id);
1233            let (d_load, d_deps, d_rec_deps) = asset_server.get_load_states(d_id).unwrap();
1234
1235            if !d_load.is_failed() {
1236                // wait until d has exited the loading state
1237                return None;
1238            }
1239
1240            assert!(d_text.is_none());
1241            assert!(d_load.is_failed());
1242            assert!(d_deps.is_failed());
1243            assert!(d_rec_deps.is_failed());
1244
1245            assert_eq!(a_text.text, "a");
1246            assert!(a_load.is_loaded());
1247            assert!(a_deps.is_loaded());
1248            assert!(a_rec_deps.is_failed());
1249
1250            assert_eq!(b_text.text, "b");
1251            assert!(b_load.is_loaded());
1252            assert!(b_deps.is_loaded());
1253            assert!(b_rec_deps.is_loaded());
1254
1255            assert_eq!(c_text.text, "c");
1256            assert!(c_load.is_loaded());
1257            assert!(c_deps.is_failed());
1258            assert!(c_rec_deps.is_failed());
1259
1260            assert!(asset_server.load_state(a_id).is_loaded());
1261            assert!(asset_server.dependency_load_state(a_id).is_loaded());
1262            assert!(asset_server
1263                .recursive_dependency_load_state(a_id)
1264                .is_failed());
1265
1266            assert!(asset_server.is_loaded(a_id));
1267            assert!(asset_server.is_loaded_with_direct_dependencies(a_id));
1268            assert!(!asset_server.is_loaded_with_dependencies(a_id));
1269
1270            Some(())
1271        });
1272    }
1273
1274    #[test]
1275    fn dependency_load_states() {
1276        // The particular usage of GatedReader in this test will cause deadlocking if running single-threaded
1277        #[cfg(not(feature = "multi_threaded"))]
1278        panic!("This test requires the \"multi_threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi_threaded");
1279
1280        let a_path = "a.cool.ron";
1281        let a_ron = r#"
1282(
1283    text: "a",
1284    dependencies: [
1285        "b.cool.ron",
1286        "c.cool.ron",
1287    ],
1288    embedded_dependencies: [],
1289    sub_texts: []
1290)"#;
1291        let b_path = "b.cool.ron";
1292        let b_ron = r#"
1293(
1294    text: "b",
1295    dependencies: [],
1296    MALFORMED
1297    embedded_dependencies: [],
1298    sub_texts: []
1299)"#;
1300
1301        let c_path = "c.cool.ron";
1302        let c_ron = r#"
1303(
1304    text: "c",
1305    dependencies: [],
1306    embedded_dependencies: [],
1307    sub_texts: []
1308)"#;
1309
1310        let dir = Dir::default();
1311        dir.insert_asset_text(Path::new(a_path), a_ron);
1312        dir.insert_asset_text(Path::new(b_path), b_ron);
1313        dir.insert_asset_text(Path::new(c_path), c_ron);
1314
1315        let (mut app, gate_opener) = test_app(dir);
1316        app.init_asset::<CoolText>()
1317            .register_asset_loader(CoolTextLoader);
1318        let asset_server = app.world().resource::<AssetServer>().clone();
1319        let handle: Handle<CoolText> = asset_server.load(a_path);
1320        let a_id = handle.id();
1321
1322        gate_opener.open(a_path);
1323        run_app_until(&mut app, |world| {
1324            let _a_text = get::<CoolText>(world, a_id)?;
1325            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1326            assert!(a_load.is_loaded());
1327            assert!(a_deps.is_loading());
1328            assert!(a_rec_deps.is_loading());
1329            Some(())
1330        });
1331
1332        gate_opener.open(b_path);
1333        run_app_until(&mut app, |world| {
1334            let a_text = get::<CoolText>(world, a_id)?;
1335            let b_id = a_text.dependencies[0].id();
1336
1337            let (b_load, _b_deps, _b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
1338            if !b_load.is_failed() {
1339                // wait until b fails
1340                return None;
1341            }
1342
1343            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1344            assert!(a_load.is_loaded());
1345            assert!(a_deps.is_failed());
1346            assert!(a_rec_deps.is_failed());
1347            Some(())
1348        });
1349
1350        gate_opener.open(c_path);
1351        run_app_until(&mut app, |world| {
1352            let a_text = get::<CoolText>(world, a_id)?;
1353            let c_id = a_text.dependencies[1].id();
1354            // wait until c loads
1355            let _c_text = get::<CoolText>(world, c_id)?;
1356
1357            let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
1358            assert!(a_load.is_loaded());
1359            assert!(
1360                a_deps.is_failed(),
1361                "Successful dependency load should not overwrite a previous failure"
1362            );
1363            assert!(
1364                a_rec_deps.is_failed(),
1365                "Successful dependency load should not overwrite a previous failure"
1366            );
1367            Some(())
1368        });
1369    }
1370
1371    const SIMPLE_TEXT: &str = r#"
1372(
1373    text: "dep",
1374    dependencies: [],
1375    embedded_dependencies: [],
1376    sub_texts: [],
1377)"#;
1378    #[test]
1379    fn keep_gotten_strong_handles() {
1380        let dir = Dir::default();
1381        dir.insert_asset_text(Path::new("dep.cool.ron"), SIMPLE_TEXT);
1382
1383        let (mut app, _) = test_app(dir);
1384        app.init_asset::<CoolText>()
1385            .init_asset::<SubText>()
1386            .init_resource::<StoredEvents>()
1387            .register_asset_loader(CoolTextLoader)
1388            .add_systems(Update, store_asset_events);
1389
1390        let id = {
1391            let handle = {
1392                let mut texts = app.world_mut().resource_mut::<Assets<CoolText>>();
1393                let handle = texts.add(CoolText::default());
1394                texts.get_strong_handle(handle.id()).unwrap()
1395            };
1396
1397            app.update();
1398
1399            {
1400                let text = app.world().resource::<Assets<CoolText>>().get(&handle);
1401                assert!(text.is_some());
1402            }
1403            handle.id()
1404        };
1405        // handle is dropped
1406        app.update();
1407        assert!(
1408            app.world().resource::<Assets<CoolText>>().get(id).is_none(),
1409            "asset has no handles, so it should have been dropped last update"
1410        );
1411    }
1412
1413    #[test]
1414    fn manual_asset_management() {
1415        // The particular usage of GatedReader in this test will cause deadlocking if running single-threaded
1416        #[cfg(not(feature = "multi_threaded"))]
1417        panic!("This test requires the \"multi_threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi_threaded");
1418
1419        let dir = Dir::default();
1420        let dep_path = "dep.cool.ron";
1421
1422        dir.insert_asset_text(Path::new(dep_path), SIMPLE_TEXT);
1423
1424        let (mut app, gate_opener) = test_app(dir);
1425        app.init_asset::<CoolText>()
1426            .init_asset::<SubText>()
1427            .init_resource::<StoredEvents>()
1428            .register_asset_loader(CoolTextLoader)
1429            .add_systems(Update, store_asset_events);
1430
1431        let hello = "hello".to_string();
1432        let empty = "".to_string();
1433
1434        let id = {
1435            let handle = {
1436                let mut texts = app.world_mut().resource_mut::<Assets<CoolText>>();
1437                texts.add(CoolText {
1438                    text: hello.clone(),
1439                    embedded: empty.clone(),
1440                    dependencies: vec![],
1441                    sub_texts: Vec::new(),
1442                })
1443            };
1444
1445            app.update();
1446
1447            {
1448                let text = app
1449                    .world()
1450                    .resource::<Assets<CoolText>>()
1451                    .get(&handle)
1452                    .unwrap();
1453                assert_eq!(text.text, hello);
1454            }
1455            handle.id()
1456        };
1457        // handle is dropped
1458        app.update();
1459        assert!(
1460            app.world().resource::<Assets<CoolText>>().get(id).is_none(),
1461            "asset has no handles, so it should have been dropped last update"
1462        );
1463        // remove event is emitted
1464        app.update();
1465        let events = core::mem::take(&mut app.world_mut().resource_mut::<StoredEvents>().0);
1466        let expected_events = vec![
1467            AssetEvent::Added { id },
1468            AssetEvent::Unused { id },
1469            AssetEvent::Removed { id },
1470        ];
1471        assert_eq!(events, expected_events);
1472
1473        let dep_handle = app.world().resource::<AssetServer>().load(dep_path);
1474        let a = CoolText {
1475            text: "a".to_string(),
1476            embedded: empty,
1477            // this dependency is behind a manual load gate, which should prevent 'a' from emitting a LoadedWithDependencies event
1478            dependencies: vec![dep_handle.clone()],
1479            sub_texts: Vec::new(),
1480        };
1481        let a_handle = app.world().resource::<AssetServer>().load_asset(a);
1482        app.update();
1483        // TODO: ideally it doesn't take two updates for the added event to emit
1484        app.update();
1485
1486        let events = core::mem::take(&mut app.world_mut().resource_mut::<StoredEvents>().0);
1487        let expected_events = vec![AssetEvent::Added { id: a_handle.id() }];
1488        assert_eq!(events, expected_events);
1489
1490        gate_opener.open(dep_path);
1491        loop {
1492            app.update();
1493            let events = core::mem::take(&mut app.world_mut().resource_mut::<StoredEvents>().0);
1494            if events.is_empty() {
1495                continue;
1496            }
1497            let expected_events = vec![
1498                AssetEvent::LoadedWithDependencies {
1499                    id: dep_handle.id(),
1500                },
1501                AssetEvent::LoadedWithDependencies { id: a_handle.id() },
1502            ];
1503            assert_eq!(events, expected_events);
1504            break;
1505        }
1506        app.update();
1507        let events = core::mem::take(&mut app.world_mut().resource_mut::<StoredEvents>().0);
1508        let expected_events = vec![AssetEvent::Added {
1509            id: dep_handle.id(),
1510        }];
1511        assert_eq!(events, expected_events);
1512    }
1513
1514    #[test]
1515    fn load_folder() {
1516        // The particular usage of GatedReader in this test will cause deadlocking if running single-threaded
1517        #[cfg(not(feature = "multi_threaded"))]
1518        panic!("This test requires the \"multi_threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi_threaded");
1519
1520        let dir = Dir::default();
1521
1522        let a_path = "text/a.cool.ron";
1523        let a_ron = r#"
1524(
1525    text: "a",
1526    dependencies: [
1527        "b.cool.ron",
1528    ],
1529    embedded_dependencies: [],
1530    sub_texts: [],
1531)"#;
1532        let b_path = "b.cool.ron";
1533        let b_ron = r#"
1534(
1535    text: "b",
1536    dependencies: [],
1537    embedded_dependencies: [],
1538    sub_texts: [],
1539)"#;
1540
1541        let c_path = "text/c.cool.ron";
1542        let c_ron = r#"
1543(
1544    text: "c",
1545    dependencies: [
1546    ],
1547    embedded_dependencies: [],
1548    sub_texts: [],
1549)"#;
1550        dir.insert_asset_text(Path::new(a_path), a_ron);
1551        dir.insert_asset_text(Path::new(b_path), b_ron);
1552        dir.insert_asset_text(Path::new(c_path), c_ron);
1553
1554        let (mut app, gate_opener) = test_app(dir);
1555        app.init_asset::<CoolText>()
1556            .init_asset::<SubText>()
1557            .register_asset_loader(CoolTextLoader);
1558        let asset_server = app.world().resource::<AssetServer>().clone();
1559        let handle: Handle<LoadedFolder> = asset_server.load_folder("text");
1560        gate_opener.open(a_path);
1561        gate_opener.open(b_path);
1562        gate_opener.open(c_path);
1563
1564        let mut reader = EventCursor::default();
1565        run_app_until(&mut app, |world| {
1566            let events = world.resource::<Events<AssetEvent<LoadedFolder>>>();
1567            let asset_server = world.resource::<AssetServer>();
1568            let loaded_folders = world.resource::<Assets<LoadedFolder>>();
1569            let cool_texts = world.resource::<Assets<CoolText>>();
1570            for event in reader.read(events) {
1571                if let AssetEvent::LoadedWithDependencies { id } = event {
1572                    if *id == handle.id() {
1573                        let loaded_folder = loaded_folders.get(&handle).unwrap();
1574                        let a_handle: Handle<CoolText> =
1575                            asset_server.get_handle("text/a.cool.ron").unwrap();
1576                        let c_handle: Handle<CoolText> =
1577                            asset_server.get_handle("text/c.cool.ron").unwrap();
1578
1579                        let mut found_a = false;
1580                        let mut found_c = false;
1581                        for asset_handle in &loaded_folder.handles {
1582                            if asset_handle.id() == a_handle.id().untyped() {
1583                                found_a = true;
1584                            } else if asset_handle.id() == c_handle.id().untyped() {
1585                                found_c = true;
1586                            }
1587                        }
1588                        assert!(found_a);
1589                        assert!(found_c);
1590                        assert_eq!(loaded_folder.handles.len(), 2);
1591
1592                        let a_text = cool_texts.get(&a_handle).unwrap();
1593                        let b_text = cool_texts.get(&a_text.dependencies[0]).unwrap();
1594                        let c_text = cool_texts.get(&c_handle).unwrap();
1595
1596                        assert_eq!("a", a_text.text);
1597                        assert_eq!("b", b_text.text);
1598                        assert_eq!("c", c_text.text);
1599
1600                        return Some(());
1601                    }
1602                }
1603            }
1604            None
1605        });
1606    }
1607
1608    /// Tests that `AssetLoadFailedEvent<A>` events are emitted and can be used to retry failed assets.
1609    #[test]
1610    fn load_error_events() {
1611        #[derive(Resource, Default)]
1612        struct ErrorTracker {
1613            tick: u64,
1614            failures: usize,
1615            queued_retries: Vec<(AssetPath<'static>, AssetId<CoolText>, u64)>,
1616            finished_asset: Option<AssetId<CoolText>>,
1617        }
1618
1619        fn asset_event_handler(
1620            mut events: EventReader<AssetEvent<CoolText>>,
1621            mut tracker: ResMut<ErrorTracker>,
1622        ) {
1623            for event in events.read() {
1624                if let AssetEvent::LoadedWithDependencies { id } = event {
1625                    tracker.finished_asset = Some(*id);
1626                }
1627            }
1628        }
1629
1630        fn asset_load_error_event_handler(
1631            server: Res<AssetServer>,
1632            mut errors: EventReader<AssetLoadFailedEvent<CoolText>>,
1633            mut tracker: ResMut<ErrorTracker>,
1634        ) {
1635            // In the real world, this would refer to time (not ticks)
1636            tracker.tick += 1;
1637
1638            // Retry loading past failed items
1639            let now = tracker.tick;
1640            tracker
1641                .queued_retries
1642                .retain(|(path, old_id, retry_after)| {
1643                    if now > *retry_after {
1644                        let new_handle = server.load::<CoolText>(path);
1645                        assert_eq!(&new_handle.id(), old_id);
1646                        false
1647                    } else {
1648                        true
1649                    }
1650                });
1651
1652            // Check what just failed
1653            for error in errors.read() {
1654                let (load_state, _, _) = server.get_load_states(error.id).unwrap();
1655                assert!(load_state.is_failed());
1656                assert_eq!(*error.path.source(), AssetSourceId::Name("unstable".into()));
1657                match &error.error {
1658                    AssetLoadError::AssetReaderError(read_error) => match read_error {
1659                        AssetReaderError::Io(_) => {
1660                            tracker.failures += 1;
1661                            if tracker.failures <= 2 {
1662                                // Retry in 10 ticks
1663                                tracker.queued_retries.push((
1664                                    error.path.clone(),
1665                                    error.id,
1666                                    now + 10,
1667                                ));
1668                            } else {
1669                                panic!(
1670                                    "Unexpected failure #{} (expected only 2)",
1671                                    tracker.failures
1672                                );
1673                            }
1674                        }
1675                        _ => panic!("Unexpected error type {:?}", read_error),
1676                    },
1677                    _ => panic!("Unexpected error type {:?}", error.error),
1678                }
1679            }
1680        }
1681
1682        let a_path = "text/a.cool.ron";
1683        let a_ron = r#"
1684(
1685    text: "a",
1686    dependencies: [],
1687    embedded_dependencies: [],
1688    sub_texts: [],
1689)"#;
1690
1691        let dir = Dir::default();
1692        dir.insert_asset_text(Path::new(a_path), a_ron);
1693        let unstable_reader = UnstableMemoryAssetReader::new(dir, 2);
1694
1695        let mut app = App::new();
1696        app.register_asset_source(
1697            "unstable",
1698            AssetSource::build().with_reader(move || Box::new(unstable_reader.clone())),
1699        )
1700        .add_plugins((
1701            TaskPoolPlugin::default(),
1702            LogPlugin::default(),
1703            AssetPlugin::default(),
1704        ))
1705        .init_asset::<CoolText>()
1706        .register_asset_loader(CoolTextLoader)
1707        .init_resource::<ErrorTracker>()
1708        .add_systems(
1709            Update,
1710            (asset_event_handler, asset_load_error_event_handler).chain(),
1711        );
1712
1713        let asset_server = app.world().resource::<AssetServer>().clone();
1714        let a_path = format!("unstable://{a_path}");
1715        let a_handle: Handle<CoolText> = asset_server.load(a_path);
1716        let a_id = a_handle.id();
1717
1718        run_app_until(&mut app, |world| {
1719            let tracker = world.resource::<ErrorTracker>();
1720            match tracker.finished_asset {
1721                Some(asset_id) => {
1722                    assert_eq!(asset_id, a_id);
1723                    let assets = world.resource::<Assets<CoolText>>();
1724                    let result = assets.get(asset_id).unwrap();
1725                    assert_eq!(result.text, "a");
1726                    Some(())
1727                }
1728                None => None,
1729            }
1730        });
1731    }
1732
1733    #[test]
1734    fn ignore_system_ambiguities_on_assets() {
1735        let mut app = App::new();
1736        app.add_plugins(AssetPlugin::default())
1737            .init_asset::<CoolText>();
1738
1739        fn uses_assets(_asset: ResMut<Assets<CoolText>>) {}
1740        app.add_systems(Update, (uses_assets, uses_assets));
1741        app.edit_schedule(Update, |s| {
1742            s.set_build_settings(ScheduleBuildSettings {
1743                ambiguity_detection: LogLevel::Error,
1744                ..Default::default()
1745            });
1746        });
1747
1748        // running schedule does not error on ambiguity between the 2 uses_assets systems
1749        app.world_mut().run_schedule(Update);
1750    }
1751
1752    // validate the Asset derive macro for various asset types
1753    #[derive(Asset, TypePath)]
1754    pub struct TestAsset;
1755
1756    #[allow(dead_code)]
1757    #[derive(Asset, TypePath)]
1758    pub enum EnumTestAsset {
1759        Unnamed(#[dependency] Handle<TestAsset>),
1760        Named {
1761            #[dependency]
1762            handle: Handle<TestAsset>,
1763            #[dependency]
1764            vec_handles: Vec<Handle<TestAsset>>,
1765            #[dependency]
1766            embedded: TestAsset,
1767        },
1768        StructStyle(#[dependency] TestAsset),
1769        Empty,
1770    }
1771
1772    #[allow(dead_code)]
1773    #[derive(Asset, TypePath)]
1774    pub struct StructTestAsset {
1775        #[dependency]
1776        handle: Handle<TestAsset>,
1777        #[dependency]
1778        embedded: TestAsset,
1779    }
1780
1781    #[allow(dead_code)]
1782    #[derive(Asset, TypePath)]
1783    pub struct TupleTestAsset(#[dependency] Handle<TestAsset>);
1784}