Skip to main content

bevy_asset/server/
info.rs

1use crate::{
2    meta::{AssetHash, MetaTransform},
3    Asset, AssetHandleProvider, AssetIndex, AssetLoadError, AssetPath, DependencyLoadState,
4    ErasedAssetIndex, ErasedLoadedAsset, Handle, InternalAssetEvent, LoadState,
5    RecursiveDependencyLoadState, StrongHandle, UntypedHandle,
6};
7use alloc::{
8    borrow::ToOwned,
9    boxed::Box,
10    sync::{Arc, Weak},
11    vec::Vec,
12};
13use bevy_ecs::world::World;
14use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};
15use bevy_tasks::Task;
16use bevy_utils::TypeIdMap;
17use core::{any::TypeId, task::Waker};
18use crossbeam_channel::Sender;
19use either::Either;
20use thiserror::Error;
21use tracing::warn;
22
23#[derive(Debug)]
24pub(crate) struct AssetInfo {
25    weak_handle: Weak<StrongHandle>,
26    pub(crate) path: Option<AssetPath<'static>>,
27    pub(crate) load_state: LoadState,
28    pub(crate) dep_load_state: DependencyLoadState,
29    pub(crate) rec_dep_load_state: RecursiveDependencyLoadState,
30    loading_dependencies: HashSet<ErasedAssetIndex>,
31    failed_dependencies: HashSet<ErasedAssetIndex>,
32    loading_rec_dependencies: HashSet<ErasedAssetIndex>,
33    failed_rec_dependencies: HashSet<ErasedAssetIndex>,
34    dependents_waiting_on_load: HashSet<ErasedAssetIndex>,
35    dependents_waiting_on_recursive_dep_load: HashSet<ErasedAssetIndex>,
36    /// The asset paths required to load this asset. Hashes will only be set for processed assets.
37    /// This is set using the value from [`LoadedAsset`].
38    /// This will only be populated if [`AssetInfos::watching_for_changes`] is set to `true` to
39    /// save memory.
40    ///
41    /// [`LoadedAsset`]: crate::loader::LoadedAsset
42    loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
43    /// The number of handle drops to skip for this asset.
44    /// See usage (and comments) in `get_or_create_path_handle` for context.
45    handle_drops_to_skip: usize,
46    /// List of tasks waiting for this asset to complete loading
47    pub(crate) waiting_tasks: Vec<Waker>,
48}
49
50impl AssetInfo {
51    fn new(weak_handle: Weak<StrongHandle>, path: Option<AssetPath<'static>>) -> Self {
52        Self {
53            weak_handle,
54            path,
55            load_state: LoadState::NotLoaded,
56            dep_load_state: DependencyLoadState::NotLoaded,
57            rec_dep_load_state: RecursiveDependencyLoadState::NotLoaded,
58            loading_dependencies: HashSet::default(),
59            failed_dependencies: HashSet::default(),
60            loading_rec_dependencies: HashSet::default(),
61            failed_rec_dependencies: HashSet::default(),
62            loader_dependencies: HashMap::default(),
63            dependents_waiting_on_load: HashSet::default(),
64            dependents_waiting_on_recursive_dep_load: HashSet::default(),
65            handle_drops_to_skip: 0,
66            waiting_tasks: Vec::new(),
67        }
68    }
69}
70
71/// Tracks statistics of the asset server.
72#[derive(Default, Clone, PartialEq, Eq)]
73pub(crate) struct AssetServerStats {
74    /// The number of load tasks that have been started.
75    pub(crate) started_load_tasks: usize,
76}
77
78#[derive(Default)]
79pub(crate) struct AssetInfos {
80    path_to_index: HashMap<AssetPath<'static>, TypeIdMap<AssetIndex>>,
81    infos: HashMap<ErasedAssetIndex, AssetInfo>,
82    /// If set to `true`, this informs [`AssetInfos`] to track data relevant to watching for changes (such as `load_dependents`)
83    /// This should only be set at startup.
84    pub(crate) watching_for_changes: bool,
85    /// Tracks assets that depend on the "key" asset path inside their asset loaders ("loader dependencies")
86    /// This should only be set when watching for changes to avoid unnecessary work.
87    pub(crate) loader_dependents: HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
88    /// Tracks living labeled assets for a given source asset.
89    /// This should only be set when watching for changes to avoid unnecessary work.
90    pub(crate) living_labeled_assets: HashMap<AssetPath<'static>, HashSet<Box<str>>>,
91    pub(crate) handle_providers: TypeIdMap<AssetHandleProvider>,
92    pub(crate) dependency_loaded_event_sender: TypeIdMap<fn(&mut World, AssetIndex)>,
93    pub(crate) dependency_failed_event_sender:
94        TypeIdMap<fn(&mut World, AssetIndex, AssetPath<'static>, AssetLoadError)>,
95    pub(crate) pending_tasks: HashMap<ErasedAssetIndex, Task<()>>,
96    /// The stats that have collected during usage of the asset server.
97    pub(crate) stats: AssetServerStats,
98}
99
100impl core::fmt::Debug for AssetInfos {
101    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102        f.debug_struct("AssetInfos")
103            .field("path_to_index", &self.path_to_index)
104            .field("infos", &self.infos)
105            .finish()
106    }
107}
108
109impl AssetInfos {
110    pub(crate) fn create_loading_handle_untyped(
111        &mut self,
112        type_id: TypeId,
113        type_name: &'static str,
114    ) -> UntypedHandle {
115        unwrap_with_context(
116            Self::create_handle_internal(
117                &mut self.infos,
118                &self.handle_providers,
119                &mut self.living_labeled_assets,
120                self.watching_for_changes,
121                type_id,
122                None,
123                None,
124                true,
125            ),
126            Either::Left(type_name),
127        )
128        .unwrap()
129    }
130
131    fn create_handle_internal(
132        infos: &mut HashMap<ErasedAssetIndex, AssetInfo>,
133        handle_providers: &TypeIdMap<AssetHandleProvider>,
134        living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<Box<str>>>,
135        watching_for_changes: bool,
136        type_id: TypeId,
137        path: Option<AssetPath<'static>>,
138        meta_transform: Option<MetaTransform>,
139        loading: bool,
140    ) -> Result<UntypedHandle, GetOrCreateHandleInternalError> {
141        let provider = handle_providers
142            .get(&type_id)
143            .ok_or(MissingHandleProviderError(type_id))?;
144
145        if watching_for_changes && let Some(path) = &path {
146            let mut without_label = path.to_owned();
147            if let Some(label) = without_label.take_label() {
148                let labels = living_labeled_assets.entry(without_label).or_default();
149                labels.insert(label.as_ref().into());
150            }
151        }
152
153        let handle = provider.reserve_handle_internal(true, path.clone(), meta_transform);
154        let mut info = AssetInfo::new(Arc::downgrade(&handle), path);
155        if loading {
156            info.load_state = LoadState::Loading;
157            info.dep_load_state = DependencyLoadState::Loading;
158            info.rec_dep_load_state = RecursiveDependencyLoadState::Loading;
159        }
160        infos.insert(ErasedAssetIndex::new(handle.index, handle.type_id), info);
161
162        Ok(UntypedHandle::Strong(handle))
163    }
164
165    pub(crate) fn get_or_create_path_handle<A: Asset>(
166        &mut self,
167        path: AssetPath<'static>,
168        loading_mode: HandleLoadingMode,
169        meta_transform: Option<MetaTransform>,
170    ) -> (Handle<A>, bool) {
171        let result = self.get_or_create_path_handle_internal(
172            path,
173            Some(TypeId::of::<A>()),
174            loading_mode,
175            meta_transform,
176        );
177        // it is ok to unwrap because TypeId was specified above
178        let (handle, should_load) =
179            unwrap_with_context(result, Either::Left(core::any::type_name::<A>())).unwrap();
180        (handle.typed_unchecked(), should_load)
181    }
182
183    pub(crate) fn get_or_create_path_handle_erased(
184        &mut self,
185        path: AssetPath<'static>,
186        type_id: TypeId,
187        type_name: Option<&str>,
188        loading_mode: HandleLoadingMode,
189        meta_transform: Option<MetaTransform>,
190    ) -> (UntypedHandle, bool) {
191        let result = self.get_or_create_path_handle_internal(
192            path,
193            Some(type_id),
194            loading_mode,
195            meta_transform,
196        );
197        let type_info = match type_name {
198            Some(type_name) => Either::Left(type_name),
199            None => Either::Right(type_id),
200        };
201        unwrap_with_context(result, type_info)
202            .expect("type should be correct since the `TypeId` is specified above")
203    }
204
205    /// Retrieves asset tracking data, or creates it if it doesn't exist.
206    /// Returns true if an asset load should be kicked off
207    pub(crate) fn get_or_create_path_handle_internal(
208        &mut self,
209        path: AssetPath<'static>,
210        type_id: Option<TypeId>,
211        loading_mode: HandleLoadingMode,
212        meta_transform: Option<MetaTransform>,
213    ) -> Result<(UntypedHandle, bool), GetOrCreateHandleInternalError> {
214        let handles = self.path_to_index.entry(path.clone()).or_default();
215
216        let type_id = type_id
217            .or_else(|| {
218                // If a TypeId is not provided, we may be able to infer it if only a single entry exists
219                if handles.len() == 1 {
220                    Some(*handles.keys().next().unwrap())
221                } else {
222                    None
223                }
224            })
225            .ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?;
226
227        match handles.entry(type_id) {
228            Entry::Occupied(entry) => {
229                let index = *entry.get();
230                // if there is a path_to_id entry, info always exists
231                let info = self
232                    .infos
233                    .get_mut(&ErasedAssetIndex::new(index, type_id))
234                    .unwrap();
235                let mut should_load = false;
236                if loading_mode == HandleLoadingMode::Force
237                    || (loading_mode == HandleLoadingMode::Request
238                        && matches!(info.load_state, LoadState::NotLoaded | LoadState::Failed(_)))
239                {
240                    info.load_state = LoadState::Loading;
241                    info.dep_load_state = DependencyLoadState::Loading;
242                    info.rec_dep_load_state = RecursiveDependencyLoadState::Loading;
243                    should_load = true;
244                }
245
246                if let Some(strong_handle) = info.weak_handle.upgrade() {
247                    // If we can upgrade the handle, there is at least one live handle right now,
248                    // The asset load has already kicked off (and maybe completed), so we can just
249                    // return a strong handle
250                    Ok((UntypedHandle::Strong(strong_handle), should_load))
251                } else {
252                    // Asset meta exists, but all live handles were dropped. This means the `track_assets` system
253                    // hasn't been run yet to remove the current asset
254                    // (note that this is guaranteed to be transactional with the `track_assets` system
255                    // because it locks the AssetInfos collection)
256
257                    // We must create a new strong handle for the existing id and ensure that the drop of the old
258                    // strong handle doesn't remove the asset from the Assets collection
259                    info.handle_drops_to_skip += 1;
260                    let provider = self
261                        .handle_providers
262                        .get(&type_id)
263                        .ok_or(MissingHandleProviderError(type_id))?;
264                    let handle = provider.get_handle(index, true, Some(path), meta_transform);
265                    info.weak_handle = Arc::downgrade(&handle);
266                    Ok((UntypedHandle::Strong(handle), should_load))
267                }
268            }
269            // The entry does not exist, so this is a "fresh" asset load. We must create a new handle
270            Entry::Vacant(entry) => {
271                let should_load = match loading_mode {
272                    HandleLoadingMode::NotLoading => false,
273                    HandleLoadingMode::Request | HandleLoadingMode::Force => true,
274                };
275                let handle = Self::create_handle_internal(
276                    &mut self.infos,
277                    &self.handle_providers,
278                    &mut self.living_labeled_assets,
279                    self.watching_for_changes,
280                    type_id,
281                    Some(path),
282                    meta_transform,
283                    should_load,
284                )?;
285                let index = match &handle {
286                    UntypedHandle::Strong(handle) => handle.index,
287                    // `create_handle_internal` always returns Strong variant.
288                    UntypedHandle::Uuid { .. } => unreachable!(),
289                };
290                entry.insert(index);
291                Ok((handle, should_load))
292            }
293        }
294    }
295
296    pub(crate) fn get(&self, index: ErasedAssetIndex) -> Option<&AssetInfo> {
297        self.infos.get(&index)
298    }
299
300    pub(crate) fn contains_key(&self, index: ErasedAssetIndex) -> bool {
301        self.infos.contains_key(&index)
302    }
303
304    pub(crate) fn get_mut(&mut self, index: ErasedAssetIndex) -> Option<&mut AssetInfo> {
305        self.infos.get_mut(&index)
306    }
307
308    pub(crate) fn get_path_and_type_id_handle(
309        &self,
310        path: &AssetPath<'_>,
311        type_id: TypeId,
312    ) -> Option<UntypedHandle> {
313        let index = *self.path_to_index.get(path)?.get(&type_id)?;
314        self.get_index_handle(ErasedAssetIndex::new(index, type_id))
315    }
316
317    pub(crate) fn get_path_indices<'a>(
318        &'a self,
319        path: &'a AssetPath<'_>,
320    ) -> impl Iterator<Item = ErasedAssetIndex> + 'a {
321        /// Concrete type to allow returning an `impl Iterator` even if `self.path_to_id.get(&path)` is `None`
322        enum HandlesByPathIterator<T> {
323            None,
324            Some(T),
325        }
326
327        impl<T> Iterator for HandlesByPathIterator<T>
328        where
329            T: Iterator<Item = ErasedAssetIndex>,
330        {
331            type Item = ErasedAssetIndex;
332
333            fn next(&mut self) -> Option<Self::Item> {
334                match self {
335                    HandlesByPathIterator::None => None,
336                    HandlesByPathIterator::Some(iter) => iter.next(),
337                }
338            }
339        }
340
341        if let Some(type_id_to_id) = self.path_to_index.get(path) {
342            HandlesByPathIterator::Some(
343                type_id_to_id
344                    .iter()
345                    .map(|(type_id, index)| ErasedAssetIndex::new(*index, *type_id)),
346            )
347        } else {
348            HandlesByPathIterator::None
349        }
350    }
351
352    pub(crate) fn get_path_handles<'a>(
353        &'a self,
354        path: &'a AssetPath<'_>,
355    ) -> impl Iterator<Item = UntypedHandle> + 'a {
356        self.get_path_indices(path)
357            .filter_map(|id| self.get_index_handle(id))
358    }
359
360    pub(crate) fn get_index_handle(&self, index: ErasedAssetIndex) -> Option<UntypedHandle> {
361        let info = self.infos.get(&index)?;
362        let strong_handle = info.weak_handle.upgrade()?;
363        Some(UntypedHandle::Strong(strong_handle))
364    }
365
366    /// Returns `true` if the asset this path points to is still alive
367    pub(crate) fn is_path_alive<'a>(&self, path: impl Into<AssetPath<'a>>) -> bool {
368        self.get_path_indices(&path.into())
369            .filter_map(|id| self.infos.get(&id))
370            .any(|info| info.weak_handle.strong_count() > 0)
371    }
372
373    /// Returns `true` if the asset at this path should be reloaded
374    pub(crate) fn should_reload(&self, path: &AssetPath) -> bool {
375        if self.is_path_alive(path) {
376            return true;
377        }
378
379        if let Some(living) = self.living_labeled_assets.get(path) {
380            !living.is_empty()
381        } else {
382            false
383        }
384    }
385
386    /// Returns `true` if the asset should be removed from the collection.
387    pub(crate) fn process_handle_drop(&mut self, index: ErasedAssetIndex) -> bool {
388        Self::process_handle_drop_internal(
389            &mut self.infos,
390            &mut self.path_to_index,
391            &mut self.loader_dependents,
392            &mut self.living_labeled_assets,
393            &mut self.pending_tasks,
394            self.watching_for_changes,
395            index,
396        )
397    }
398
399    /// Updates [`AssetInfo`] / load state for an asset that has finished loading (and relevant dependencies / dependents).
400    pub(crate) fn process_asset_load(
401        &mut self,
402        loaded_asset_index: ErasedAssetIndex,
403        loaded_asset: ErasedLoadedAsset,
404        world: &mut World,
405        sender: &Sender<InternalAssetEvent>,
406    ) {
407        // Process all the labeled assets first so that they don't get skipped due to the "parent"
408        // not having its handle alive.
409        for (_, asset) in loaded_asset.labeled_assets {
410            let UntypedHandle::Strong(handle) = &asset.handle else {
411                unreachable!("Labeled assets are always strong handles");
412            };
413            self.process_asset_load(
414                ErasedAssetIndex {
415                    index: handle.index,
416                    type_id: handle.type_id,
417                },
418                asset.asset,
419                world,
420                sender,
421            );
422        }
423
424        // Check whether the handle has been dropped since the asset was loaded.
425        if !self.infos.contains_key(&loaded_asset_index) {
426            return;
427        }
428
429        loaded_asset.value.insert(loaded_asset_index.index, world);
430        let mut loading_deps = loaded_asset.dependencies;
431        let mut failed_deps = <HashSet<_>>::default();
432        let mut dep_error = None;
433        let mut loading_rec_deps = loading_deps.clone();
434        let mut failed_rec_deps = <HashSet<_>>::default();
435        let mut rec_dep_error = None;
436        loading_deps.retain(|dep_id| {
437            if let Some(dep_info) = self.get_mut(*dep_id) {
438                match dep_info.rec_dep_load_state {
439                    RecursiveDependencyLoadState::Loading
440                    | RecursiveDependencyLoadState::NotLoaded => {
441                        // If dependency is loading, wait for it.
442                        dep_info
443                            .dependents_waiting_on_recursive_dep_load
444                            .insert(loaded_asset_index);
445                    }
446                    RecursiveDependencyLoadState::Loaded => {
447                        // If dependency is loaded, reduce our count by one
448                        loading_rec_deps.remove(dep_id);
449                    }
450                    RecursiveDependencyLoadState::Failed(ref error) => {
451                        if rec_dep_error.is_none() {
452                            rec_dep_error = Some(error.clone());
453                        }
454                        failed_rec_deps.insert(*dep_id);
455                        loading_rec_deps.remove(dep_id);
456                    }
457                }
458                match dep_info.load_state {
459                    LoadState::NotLoaded | LoadState::Loading => {
460                        // If dependency is loading, wait for it.
461                        dep_info.dependents_waiting_on_load.insert(loaded_asset_index);
462                        true
463                    }
464                    LoadState::Loaded => {
465                        // If dependency is loaded, reduce our count by one
466                        false
467                    }
468                    LoadState::Failed(ref error) => {
469                        if dep_error.is_none() {
470                            dep_error = Some(error.clone());
471                        }
472                        failed_deps.insert(*dep_id);
473                        false
474                    }
475                }
476            } else {
477                // the dependency id does not exist, which implies it was manually removed or never existed in the first place
478                warn!(
479                    "Dependency {} from asset {} is unknown. This asset's dependency load status will not switch to 'Loaded' until the unknown dependency is loaded.",
480                    dep_id, loaded_asset_index
481                );
482                true
483            }
484        });
485
486        let dep_load_state = match (loading_deps.len(), failed_deps.len()) {
487            (0, 0) => DependencyLoadState::Loaded,
488            (_loading, 0) => DependencyLoadState::Loading,
489            (_loading, _failed) => DependencyLoadState::Failed(dep_error.unwrap()),
490        };
491
492        let rec_dep_load_state = match (loading_rec_deps.len(), failed_rec_deps.len()) {
493            (0, 0) => {
494                sender
495                    .send(InternalAssetEvent::LoadedWithDependencies {
496                        index: loaded_asset_index,
497                    })
498                    .unwrap();
499                RecursiveDependencyLoadState::Loaded
500            }
501            (_loading, 0) => RecursiveDependencyLoadState::Loading,
502            (_loading, _failed) => RecursiveDependencyLoadState::Failed(rec_dep_error.unwrap()),
503        };
504
505        let (dependents_waiting_on_load, dependents_waiting_on_rec_load) = {
506            let watching_for_changes = self.watching_for_changes;
507            // if watching for changes, track reverse loader dependencies for hot reloading
508            if watching_for_changes {
509                let info = self
510                    .infos
511                    .get(&loaded_asset_index)
512                    .expect("Asset info should always exist at this point");
513                if let Some(asset_path) = &info.path {
514                    for loader_dependency in loaded_asset.loader_dependencies.keys() {
515                        let dependents = self
516                            .loader_dependents
517                            .entry(loader_dependency.clone())
518                            .or_default();
519                        dependents.insert(asset_path.clone());
520                    }
521                }
522            }
523            let info = self
524                .get_mut(loaded_asset_index)
525                .expect("Asset info should always exist at this point");
526            info.loading_dependencies = loading_deps;
527            info.failed_dependencies = failed_deps;
528            info.loading_rec_dependencies = loading_rec_deps;
529            info.failed_rec_dependencies = failed_rec_deps;
530            info.load_state = LoadState::Loaded;
531            info.dep_load_state = dep_load_state;
532            info.rec_dep_load_state = rec_dep_load_state.clone();
533            if watching_for_changes {
534                info.loader_dependencies = loaded_asset.loader_dependencies;
535            }
536
537            let dependents_waiting_on_rec_load =
538                if rec_dep_load_state.is_loaded() || rec_dep_load_state.is_failed() {
539                    Some(core::mem::take(
540                        &mut info.dependents_waiting_on_recursive_dep_load,
541                    ))
542                } else {
543                    None
544                };
545
546            (
547                core::mem::take(&mut info.dependents_waiting_on_load),
548                dependents_waiting_on_rec_load,
549            )
550        };
551
552        for id in dependents_waiting_on_load {
553            if let Some(info) = self.get_mut(id) {
554                info.loading_dependencies.remove(&loaded_asset_index);
555                if info.loading_dependencies.is_empty() && !info.dep_load_state.is_failed() {
556                    // send dependencies loaded event
557                    info.dep_load_state = DependencyLoadState::Loaded;
558                }
559            }
560        }
561
562        if let Some(dependents_waiting_on_rec_load) = dependents_waiting_on_rec_load {
563            match rec_dep_load_state {
564                RecursiveDependencyLoadState::Loaded => {
565                    for dep_id in dependents_waiting_on_rec_load {
566                        Self::propagate_loaded_state(self, loaded_asset_index, dep_id, sender);
567                    }
568                }
569                RecursiveDependencyLoadState::Failed(ref error) => {
570                    for dep_id in dependents_waiting_on_rec_load {
571                        Self::propagate_failed_state(self, loaded_asset_index, dep_id, error);
572                    }
573                }
574                RecursiveDependencyLoadState::Loading | RecursiveDependencyLoadState::NotLoaded => {
575                    // dependents_waiting_on_rec_load should be None in this case
576                    unreachable!("`Loading` and `NotLoaded` state should never be propagated.")
577                }
578            }
579        }
580    }
581
582    /// Recursively propagates loaded state up the dependency tree.
583    fn propagate_loaded_state(
584        infos: &mut AssetInfos,
585        loaded_id: ErasedAssetIndex,
586        waiting_id: ErasedAssetIndex,
587        sender: &Sender<InternalAssetEvent>,
588    ) {
589        let dependents_waiting_on_rec_load = if let Some(info) = infos.get_mut(waiting_id) {
590            info.loading_rec_dependencies.remove(&loaded_id);
591            if info.loading_rec_dependencies.is_empty() && info.failed_rec_dependencies.is_empty() {
592                info.rec_dep_load_state = RecursiveDependencyLoadState::Loaded;
593                if info.load_state.is_loaded() {
594                    sender
595                        .send(InternalAssetEvent::LoadedWithDependencies { index: waiting_id })
596                        .unwrap();
597                }
598                Some(core::mem::take(
599                    &mut info.dependents_waiting_on_recursive_dep_load,
600                ))
601            } else {
602                None
603            }
604        } else {
605            None
606        };
607
608        if let Some(dependents_waiting_on_rec_load) = dependents_waiting_on_rec_load {
609            for dep_id in dependents_waiting_on_rec_load {
610                Self::propagate_loaded_state(infos, waiting_id, dep_id, sender);
611            }
612        }
613    }
614
615    /// Recursively propagates failed state up the dependency tree
616    fn propagate_failed_state(
617        infos: &mut AssetInfos,
618        failed_id: ErasedAssetIndex,
619        waiting_id: ErasedAssetIndex,
620        error: &Arc<AssetLoadError>,
621    ) {
622        let dependents_waiting_on_rec_load = if let Some(info) = infos.get_mut(waiting_id) {
623            info.loading_rec_dependencies.remove(&failed_id);
624            info.failed_rec_dependencies.insert(failed_id);
625            info.rec_dep_load_state = RecursiveDependencyLoadState::Failed(error.clone());
626            Some(core::mem::take(
627                &mut info.dependents_waiting_on_recursive_dep_load,
628            ))
629        } else {
630            None
631        };
632
633        if let Some(dependents_waiting_on_rec_load) = dependents_waiting_on_rec_load {
634            for dep_id in dependents_waiting_on_rec_load {
635                Self::propagate_failed_state(infos, waiting_id, dep_id, error);
636            }
637        }
638    }
639
640    pub(crate) fn process_asset_fail(
641        &mut self,
642        failed_index: ErasedAssetIndex,
643        error: AssetLoadError,
644    ) {
645        // Check whether the handle has been dropped since the asset was loaded.
646        if !self.infos.contains_key(&failed_index) {
647            return;
648        }
649
650        let error = Arc::new(error);
651        let (dependents_waiting_on_load, dependents_waiting_on_rec_load) = {
652            let Some(info) = self.get_mut(failed_index) else {
653                // The asset was already dropped.
654                return;
655            };
656            info.load_state = LoadState::Failed(error.clone());
657            info.dep_load_state = DependencyLoadState::Failed(error.clone());
658            info.rec_dep_load_state = RecursiveDependencyLoadState::Failed(error.clone());
659            for waker in info.waiting_tasks.drain(..) {
660                waker.wake();
661            }
662            (
663                core::mem::take(&mut info.dependents_waiting_on_load),
664                core::mem::take(&mut info.dependents_waiting_on_recursive_dep_load),
665            )
666        };
667
668        for waiting_id in dependents_waiting_on_load {
669            if let Some(info) = self.get_mut(waiting_id) {
670                info.loading_dependencies.remove(&failed_index);
671                info.failed_dependencies.insert(failed_index);
672                // don't overwrite DependencyLoadState if already failed to preserve first error
673                if !info.dep_load_state.is_failed() {
674                    info.dep_load_state = DependencyLoadState::Failed(error.clone());
675                }
676            }
677        }
678
679        for waiting_id in dependents_waiting_on_rec_load {
680            Self::propagate_failed_state(self, failed_index, waiting_id, &error);
681        }
682    }
683
684    fn remove_dependents_and_labels(
685        info: &AssetInfo,
686        loader_dependents: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
687        path: &AssetPath<'static>,
688        living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<Box<str>>>,
689    ) {
690        for loader_dependency in info.loader_dependencies.keys() {
691            if let Some(dependents) = loader_dependents.get_mut(loader_dependency) {
692                dependents.remove(path);
693            }
694        }
695
696        let Some(label) = path.label() else {
697            return;
698        };
699
700        let mut without_label = path.to_owned();
701        without_label.remove_label();
702
703        let Entry::Occupied(mut entry) = living_labeled_assets.entry(without_label) else {
704            return;
705        };
706
707        entry.get_mut().remove(label);
708        if entry.get().is_empty() {
709            entry.remove();
710        }
711    }
712
713    fn process_handle_drop_internal(
714        infos: &mut HashMap<ErasedAssetIndex, AssetInfo>,
715        path_to_id: &mut HashMap<AssetPath<'static>, TypeIdMap<AssetIndex>>,
716        loader_dependents: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
717        living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<Box<str>>>,
718        pending_tasks: &mut HashMap<ErasedAssetIndex, Task<()>>,
719        watching_for_changes: bool,
720        index: ErasedAssetIndex,
721    ) -> bool {
722        let Entry::Occupied(mut entry) = infos.entry(index) else {
723            // Either the asset was already dropped, it doesn't exist, or it isn't managed by the asset server
724            // None of these cases should result in a removal from the Assets collection
725            return false;
726        };
727
728        if entry.get_mut().handle_drops_to_skip > 0 {
729            entry.get_mut().handle_drops_to_skip -= 1;
730            return false;
731        }
732
733        pending_tasks.remove(&index);
734
735        let type_id = entry.key().type_id;
736
737        let info = entry.remove();
738        let Some(path) = &info.path else {
739            return true;
740        };
741
742        if watching_for_changes {
743            Self::remove_dependents_and_labels(
744                &info,
745                loader_dependents,
746                path,
747                living_labeled_assets,
748            );
749        }
750
751        if let Some(map) = path_to_id.get_mut(path) {
752            map.remove(&type_id);
753
754            if map.is_empty() {
755                path_to_id.remove(path);
756            }
757        };
758
759        true
760    }
761
762    /// Consumes all current handle drop events. This will update information in [`AssetInfos`], but it
763    /// will not affect [`Assets`] storages. For normal use cases, prefer `Assets::track_assets()`
764    /// This should only be called if `Assets` storage isn't being used (such as in [`AssetProcessor`](crate::processor::AssetProcessor))
765    ///
766    /// [`Assets`]: crate::Assets
767    pub(crate) fn consume_handle_drop_events(&mut self) {
768        for provider in self.handle_providers.values() {
769            while let Ok(drop_event) = provider.drop_receiver.try_recv() {
770                let id = drop_event.index;
771                if drop_event.asset_server_managed {
772                    Self::process_handle_drop_internal(
773                        &mut self.infos,
774                        &mut self.path_to_index,
775                        &mut self.loader_dependents,
776                        &mut self.living_labeled_assets,
777                        &mut self.pending_tasks,
778                        self.watching_for_changes,
779                        id,
780                    );
781                }
782            }
783        }
784    }
785}
786/// Determines how a handle should be initialized
787#[derive(Copy, Clone, PartialEq, Eq)]
788pub(crate) enum HandleLoadingMode {
789    /// The handle is for an asset that isn't loading/loaded yet.
790    NotLoading,
791    /// The handle is for an asset that is being _requested_ to load (if it isn't already loading)
792    Request,
793    /// The handle is for an asset that is being forced to load (even if it has already loaded)
794    Force,
795}
796
797#[derive(Error, Debug)]
798#[error("Cannot allocate a handle because no handle provider exists for asset type {0:?}")]
799pub struct MissingHandleProviderError(TypeId);
800
801/// An error encountered during [`AssetInfos::get_or_create_path_handle_internal`].
802#[derive(Error, Debug)]
803pub(crate) enum GetOrCreateHandleInternalError {
804    #[error(transparent)]
805    MissingHandleProviderError(#[from] MissingHandleProviderError),
806    #[error("Handle does not exist but TypeId was not specified.")]
807    HandleMissingButTypeIdNotSpecified,
808}
809
810pub(crate) fn unwrap_with_context<T>(
811    result: Result<T, GetOrCreateHandleInternalError>,
812    type_info: Either<&str, TypeId>,
813) -> Option<T> {
814    match result {
815        Ok(value) => Some(value),
816        Err(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified) => None,
817        Err(GetOrCreateHandleInternalError::MissingHandleProviderError(_)) => match type_info {
818            Either::Left(type_name) => {
819                panic!("Cannot allocate an Asset Handle of type '{type_name}' because the asset type has not been initialized. \
820                    Make sure you have called `app.init_asset::<{type_name}>()`");
821            }
822            Either::Right(type_id) => {
823                panic!("Cannot allocate an AssetHandle of type '{type_id:?}' because the asset type has not been initialized. \
824                    Make sure you have called `app.init_asset::<(actual asset type)>()`")
825            }
826        },
827    }
828}