bevy_asset/
lib.rs

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