bevy/crates/bevy_asset/src/server/info.rs
Rich Churcher fd329c0426
Allow to expect (adopted) (#15301)
# Objective

> Rust 1.81 released the #[expect(...)] attribute, which works like
#[allow(...)] but throws a warning if the lint isn't raised. This is
preferred to #[allow(...)] because it tells us when it can be removed.

- Adopts the parts of #15118 that are complete, and updates the branch
so it can be merged.
- There were a few conflicts, let me know if I misjudged any of 'em.

Alice's
[recommendation](https://github.com/bevyengine/bevy/issues/15059#issuecomment-2349263900)
seems well-taken, let's do this crate by crate now that @BD103 has done
the lion's share of this!

(Relates to, but doesn't yet completely finish #15059.)

Crates this _doesn't_ cover:

- bevy_input
- bevy_gilrs
- bevy_window
- bevy_winit
- bevy_state
- bevy_render
- bevy_picking
- bevy_core_pipeline
- bevy_sprite
- bevy_text
- bevy_pbr
- bevy_ui
- bevy_gltf
- bevy_gizmos
- bevy_dev_tools
- bevy_internal
- bevy_dylib

---------

Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com>
Co-authored-by: Ben Frankel <ben.frankel7@gmail.com>
Co-authored-by: Antony <antony.m.3012@gmail.com>
2024-09-20 19:16:42 +00:00

782 lines
31 KiB
Rust

use crate::{
meta::{AssetHash, MetaTransform},
Asset, AssetHandleProvider, AssetLoadError, AssetPath, DependencyLoadState, ErasedLoadedAsset,
Handle, InternalAssetEvent, LoadState, RecursiveDependencyLoadState, StrongHandle,
UntypedAssetId, UntypedHandle,
};
use bevy_ecs::world::World;
use bevy_tasks::Task;
use bevy_utils::tracing::warn;
use bevy_utils::{Entry, HashMap, HashSet, TypeIdMap};
use crossbeam_channel::Sender;
use std::{
any::TypeId,
sync::{Arc, Weak},
};
use thiserror::Error;
#[derive(Debug)]
pub(crate) struct AssetInfo {
weak_handle: Weak<StrongHandle>,
pub(crate) path: Option<AssetPath<'static>>,
pub(crate) load_state: LoadState,
pub(crate) dep_load_state: DependencyLoadState,
pub(crate) rec_dep_load_state: RecursiveDependencyLoadState,
loading_dependencies: HashSet<UntypedAssetId>,
failed_dependencies: HashSet<UntypedAssetId>,
loading_rec_dependencies: HashSet<UntypedAssetId>,
failed_rec_dependencies: HashSet<UntypedAssetId>,
dependants_waiting_on_load: HashSet<UntypedAssetId>,
dependants_waiting_on_recursive_dep_load: HashSet<UntypedAssetId>,
/// The asset paths required to load this asset. Hashes will only be set for processed assets.
/// This is set using the value from [`LoadedAsset`].
/// This will only be populated if [`AssetInfos::watching_for_changes`] is set to `true` to
/// save memory.
///
/// [`LoadedAsset`]: crate::loader::LoadedAsset
loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
/// The number of handle drops to skip for this asset.
/// See usage (and comments) in `get_or_create_path_handle` for context.
handle_drops_to_skip: usize,
}
impl AssetInfo {
fn new(weak_handle: Weak<StrongHandle>, path: Option<AssetPath<'static>>) -> Self {
Self {
weak_handle,
path,
load_state: LoadState::NotLoaded,
dep_load_state: DependencyLoadState::NotLoaded,
rec_dep_load_state: RecursiveDependencyLoadState::NotLoaded,
loading_dependencies: HashSet::default(),
failed_dependencies: HashSet::default(),
loading_rec_dependencies: HashSet::default(),
failed_rec_dependencies: HashSet::default(),
loader_dependencies: HashMap::default(),
dependants_waiting_on_load: HashSet::default(),
dependants_waiting_on_recursive_dep_load: HashSet::default(),
handle_drops_to_skip: 0,
}
}
}
#[derive(Default)]
pub(crate) struct AssetInfos {
path_to_id: HashMap<AssetPath<'static>, TypeIdMap<UntypedAssetId>>,
infos: HashMap<UntypedAssetId, AssetInfo>,
/// If set to `true`, this informs [`AssetInfos`] to track data relevant to watching for changes (such as `load_dependants`)
/// This should only be set at startup.
pub(crate) watching_for_changes: bool,
/// Tracks assets that depend on the "key" asset path inside their asset loaders ("loader dependencies")
/// This should only be set when watching for changes to avoid unnecessary work.
pub(crate) loader_dependants: HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
/// Tracks living labeled assets for a given source asset.
/// This should only be set when watching for changes to avoid unnecessary work.
pub(crate) living_labeled_assets: HashMap<AssetPath<'static>, HashSet<Box<str>>>,
pub(crate) handle_providers: TypeIdMap<AssetHandleProvider>,
pub(crate) dependency_loaded_event_sender: TypeIdMap<fn(&mut World, UntypedAssetId)>,
pub(crate) dependency_failed_event_sender:
TypeIdMap<fn(&mut World, UntypedAssetId, AssetPath<'static>, AssetLoadError)>,
pub(crate) pending_tasks: HashMap<UntypedAssetId, Task<()>>,
}
impl std::fmt::Debug for AssetInfos {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AssetInfos")
.field("path_to_id", &self.path_to_id)
.field("infos", &self.infos)
.finish()
}
}
impl AssetInfos {
pub(crate) fn create_loading_handle_untyped(
&mut self,
type_id: TypeId,
type_name: &'static str,
) -> UntypedHandle {
unwrap_with_context(
Self::create_handle_internal(
&mut self.infos,
&self.handle_providers,
&mut self.living_labeled_assets,
self.watching_for_changes,
type_id,
None,
None,
true,
),
type_name,
)
.unwrap()
}
#[expect(
clippy::too_many_arguments,
reason = "Arguments needed so that both `create_loading_handle_untyped()` and `get_or_create_path_handle_internal()` may share code."
)]
fn create_handle_internal(
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
handle_providers: &TypeIdMap<AssetHandleProvider>,
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<Box<str>>>,
watching_for_changes: bool,
type_id: TypeId,
path: Option<AssetPath<'static>>,
meta_transform: Option<MetaTransform>,
loading: bool,
) -> Result<UntypedHandle, GetOrCreateHandleInternalError> {
let provider = handle_providers
.get(&type_id)
.ok_or(MissingHandleProviderError(type_id))?;
if watching_for_changes {
if let Some(path) = &path {
let mut without_label = path.to_owned();
if let Some(label) = without_label.take_label() {
let labels = living_labeled_assets.entry(without_label).or_default();
labels.insert(label.as_ref().into());
}
}
}
let handle = provider.reserve_handle_internal(true, path.clone(), meta_transform);
let mut info = AssetInfo::new(Arc::downgrade(&handle), path);
if loading {
info.load_state = LoadState::Loading;
info.dep_load_state = DependencyLoadState::Loading;
info.rec_dep_load_state = RecursiveDependencyLoadState::Loading;
}
infos.insert(handle.id, info);
Ok(UntypedHandle::Strong(handle))
}
pub(crate) fn get_or_create_path_handle<A: Asset>(
&mut self,
path: AssetPath<'static>,
loading_mode: HandleLoadingMode,
meta_transform: Option<MetaTransform>,
) -> (Handle<A>, bool) {
let result = self.get_or_create_path_handle_internal(
path,
Some(TypeId::of::<A>()),
loading_mode,
meta_transform,
);
// it is ok to unwrap because TypeId was specified above
let (handle, should_load) =
unwrap_with_context(result, std::any::type_name::<A>()).unwrap();
(handle.typed_unchecked(), should_load)
}
pub(crate) fn get_or_create_path_handle_untyped(
&mut self,
path: AssetPath<'static>,
type_id: TypeId,
type_name: &'static str,
loading_mode: HandleLoadingMode,
meta_transform: Option<MetaTransform>,
) -> (UntypedHandle, bool) {
let result = self.get_or_create_path_handle_internal(
path,
Some(type_id),
loading_mode,
meta_transform,
);
// it is ok to unwrap because TypeId was specified above
unwrap_with_context(result, type_name).unwrap()
}
/// Retrieves asset tracking data, or creates it if it doesn't exist.
/// Returns true if an asset load should be kicked off
pub(crate) fn get_or_create_path_handle_internal(
&mut self,
path: AssetPath<'static>,
type_id: Option<TypeId>,
loading_mode: HandleLoadingMode,
meta_transform: Option<MetaTransform>,
) -> Result<(UntypedHandle, bool), GetOrCreateHandleInternalError> {
let handles = self.path_to_id.entry(path.clone()).or_default();
let type_id = type_id
.or_else(|| {
// If a TypeId is not provided, we may be able to infer it if only a single entry exists
if handles.len() == 1 {
Some(*handles.keys().next().unwrap())
} else {
None
}
})
.ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?;
match handles.entry(type_id) {
Entry::Occupied(entry) => {
let id = *entry.get();
// if there is a path_to_id entry, info always exists
let info = self.infos.get_mut(&id).unwrap();
let mut should_load = false;
if loading_mode == HandleLoadingMode::Force
|| (loading_mode == HandleLoadingMode::Request
&& matches!(info.load_state, LoadState::NotLoaded | LoadState::Failed(_)))
{
info.load_state = LoadState::Loading;
info.dep_load_state = DependencyLoadState::Loading;
info.rec_dep_load_state = RecursiveDependencyLoadState::Loading;
should_load = true;
}
if let Some(strong_handle) = info.weak_handle.upgrade() {
// If we can upgrade the handle, there is at least one live handle right now,
// The asset load has already kicked off (and maybe completed), so we can just
// return a strong handle
Ok((UntypedHandle::Strong(strong_handle), should_load))
} else {
// Asset meta exists, but all live handles were dropped. This means the `track_assets` system
// hasn't been run yet to remove the current asset
// (note that this is guaranteed to be transactional with the `track_assets` system because
// because it locks the AssetInfos collection)
// We must create a new strong handle for the existing id and ensure that the drop of the old
// strong handle doesn't remove the asset from the Assets collection
info.handle_drops_to_skip += 1;
let provider = self
.handle_providers
.get(&type_id)
.ok_or(MissingHandleProviderError(type_id))?;
let handle =
provider.get_handle(id.internal(), true, Some(path), meta_transform);
info.weak_handle = Arc::downgrade(&handle);
Ok((UntypedHandle::Strong(handle), should_load))
}
}
// The entry does not exist, so this is a "fresh" asset load. We must create a new handle
Entry::Vacant(entry) => {
let should_load = match loading_mode {
HandleLoadingMode::NotLoading => false,
HandleLoadingMode::Request | HandleLoadingMode::Force => true,
};
let handle = Self::create_handle_internal(
&mut self.infos,
&self.handle_providers,
&mut self.living_labeled_assets,
self.watching_for_changes,
type_id,
Some(path),
meta_transform,
should_load,
)?;
entry.insert(handle.id());
Ok((handle, should_load))
}
}
}
pub(crate) fn get(&self, id: UntypedAssetId) -> Option<&AssetInfo> {
self.infos.get(&id)
}
pub(crate) fn contains_key(&self, id: UntypedAssetId) -> bool {
self.infos.contains_key(&id)
}
pub(crate) fn get_mut(&mut self, id: UntypedAssetId) -> Option<&mut AssetInfo> {
self.infos.get_mut(&id)
}
pub(crate) fn get_path_and_type_id_handle(
&self,
path: &AssetPath,
type_id: TypeId,
) -> Option<UntypedHandle> {
let id = self.path_to_id.get(path)?.get(&type_id)?;
self.get_id_handle(*id)
}
pub(crate) fn get_path_ids<'a>(
&'a self,
path: &'a AssetPath<'a>,
) -> impl Iterator<Item = UntypedAssetId> + 'a {
/// Concrete type to allow returning an `impl Iterator` even if `self.path_to_id.get(&path)` is `None`
enum HandlesByPathIterator<T> {
None,
Some(T),
}
impl<T> Iterator for HandlesByPathIterator<T>
where
T: Iterator<Item = UntypedAssetId>,
{
type Item = UntypedAssetId;
fn next(&mut self) -> Option<Self::Item> {
match self {
HandlesByPathIterator::None => None,
HandlesByPathIterator::Some(iter) => iter.next(),
}
}
}
if let Some(type_id_to_id) = self.path_to_id.get(path) {
HandlesByPathIterator::Some(type_id_to_id.values().copied())
} else {
HandlesByPathIterator::None
}
}
pub(crate) fn get_path_handles<'a>(
&'a self,
path: &'a AssetPath<'a>,
) -> impl Iterator<Item = UntypedHandle> + 'a {
self.get_path_ids(path)
.filter_map(|id| self.get_id_handle(id))
}
pub(crate) fn get_id_handle(&self, id: UntypedAssetId) -> Option<UntypedHandle> {
let info = self.infos.get(&id)?;
let strong_handle = info.weak_handle.upgrade()?;
Some(UntypedHandle::Strong(strong_handle))
}
/// Returns `true` if the asset this path points to is still alive
pub(crate) fn is_path_alive<'a>(&self, path: impl Into<AssetPath<'a>>) -> bool {
let path = path.into();
let result = self
.get_path_ids(&path)
.filter_map(|id| self.infos.get(&id))
.any(|info| info.weak_handle.strong_count() > 0);
result
}
/// Returns `true` if the asset at this path should be reloaded
pub(crate) fn should_reload(&self, path: &AssetPath) -> bool {
if self.is_path_alive(path) {
return true;
}
if let Some(living) = self.living_labeled_assets.get(path) {
!living.is_empty()
} else {
false
}
}
/// Returns `true` if the asset should be removed from the collection.
pub(crate) fn process_handle_drop(&mut self, id: UntypedAssetId) -> bool {
Self::process_handle_drop_internal(
&mut self.infos,
&mut self.path_to_id,
&mut self.loader_dependants,
&mut self.living_labeled_assets,
&mut self.pending_tasks,
self.watching_for_changes,
id,
)
}
/// Updates [`AssetInfo`] / load state for an asset that has finished loading (and relevant dependencies / dependants).
pub(crate) fn process_asset_load(
&mut self,
loaded_asset_id: UntypedAssetId,
loaded_asset: ErasedLoadedAsset,
world: &mut World,
sender: &Sender<InternalAssetEvent>,
) {
// Check whether the handle has been dropped since the asset was loaded.
if !self.infos.contains_key(&loaded_asset_id) {
return;
}
loaded_asset.value.insert(loaded_asset_id, world);
let mut loading_deps = loaded_asset.dependencies;
let mut failed_deps = HashSet::new();
let mut dep_error = None;
let mut loading_rec_deps = loading_deps.clone();
let mut failed_rec_deps = HashSet::new();
let mut rec_dep_error = None;
loading_deps.retain(|dep_id| {
if let Some(dep_info) = self.get_mut(*dep_id) {
match dep_info.rec_dep_load_state {
RecursiveDependencyLoadState::Loading
| RecursiveDependencyLoadState::NotLoaded => {
// If dependency is loading, wait for it.
dep_info
.dependants_waiting_on_recursive_dep_load
.insert(loaded_asset_id);
}
RecursiveDependencyLoadState::Loaded => {
// If dependency is loaded, reduce our count by one
loading_rec_deps.remove(dep_id);
}
RecursiveDependencyLoadState::Failed(ref error) => {
if rec_dep_error.is_none() {
rec_dep_error = Some(error.clone());
}
failed_rec_deps.insert(*dep_id);
loading_rec_deps.remove(dep_id);
}
}
match dep_info.load_state {
LoadState::NotLoaded | LoadState::Loading => {
// If dependency is loading, wait for it.
dep_info.dependants_waiting_on_load.insert(loaded_asset_id);
true
}
LoadState::Loaded => {
// If dependency is loaded, reduce our count by one
false
}
LoadState::Failed(ref error) => {
if dep_error.is_none() {
dep_error = Some(error.clone());
}
failed_deps.insert(*dep_id);
false
}
}
} else {
// the dependency id does not exist, which implies it was manually removed or never existed in the first place
warn!(
"Dependency {:?} from asset {:?} is unknown. This asset's dependency load status will not switch to 'Loaded' until the unknown dependency is loaded.",
dep_id, loaded_asset_id
);
true
}
});
let dep_load_state = match (loading_deps.len(), failed_deps.len()) {
(0, 0) => DependencyLoadState::Loaded,
(_loading, 0) => DependencyLoadState::Loading,
(_loading, _failed) => DependencyLoadState::Failed(dep_error.unwrap()),
};
let rec_dep_load_state = match (loading_rec_deps.len(), failed_rec_deps.len()) {
(0, 0) => {
sender
.send(InternalAssetEvent::LoadedWithDependencies {
id: loaded_asset_id,
})
.unwrap();
RecursiveDependencyLoadState::Loaded
}
(_loading, 0) => RecursiveDependencyLoadState::Loading,
(_loading, _failed) => RecursiveDependencyLoadState::Failed(rec_dep_error.unwrap()),
};
let (dependants_waiting_on_load, dependants_waiting_on_rec_load) = {
let watching_for_changes = self.watching_for_changes;
// if watching for changes, track reverse loader dependencies for hot reloading
if watching_for_changes {
let info = self
.infos
.get(&loaded_asset_id)
.expect("Asset info should always exist at this point");
if let Some(asset_path) = &info.path {
for loader_dependency in loaded_asset.loader_dependencies.keys() {
let dependants = self
.loader_dependants
.entry(loader_dependency.clone())
.or_default();
dependants.insert(asset_path.clone());
}
}
}
let info = self
.get_mut(loaded_asset_id)
.expect("Asset info should always exist at this point");
info.loading_dependencies = loading_deps;
info.failed_dependencies = failed_deps;
info.loading_rec_dependencies = loading_rec_deps;
info.failed_rec_dependencies = failed_rec_deps;
info.load_state = LoadState::Loaded;
info.dep_load_state = dep_load_state;
info.rec_dep_load_state = rec_dep_load_state.clone();
if watching_for_changes {
info.loader_dependencies = loaded_asset.loader_dependencies;
}
let dependants_waiting_on_rec_load = if matches!(
rec_dep_load_state,
RecursiveDependencyLoadState::Loaded | RecursiveDependencyLoadState::Failed(_)
) {
Some(std::mem::take(
&mut info.dependants_waiting_on_recursive_dep_load,
))
} else {
None
};
(
std::mem::take(&mut info.dependants_waiting_on_load),
dependants_waiting_on_rec_load,
)
};
for id in dependants_waiting_on_load {
if let Some(info) = self.get_mut(id) {
info.loading_dependencies.remove(&loaded_asset_id);
if info.loading_dependencies.is_empty()
&& !matches!(info.dep_load_state, DependencyLoadState::Failed(_))
{
// send dependencies loaded event
info.dep_load_state = DependencyLoadState::Loaded;
}
}
}
if let Some(dependants_waiting_on_rec_load) = dependants_waiting_on_rec_load {
match rec_dep_load_state {
RecursiveDependencyLoadState::Loaded => {
for dep_id in dependants_waiting_on_rec_load {
Self::propagate_loaded_state(self, loaded_asset_id, dep_id, sender);
}
}
RecursiveDependencyLoadState::Failed(ref error) => {
for dep_id in dependants_waiting_on_rec_load {
Self::propagate_failed_state(self, loaded_asset_id, dep_id, error);
}
}
RecursiveDependencyLoadState::Loading | RecursiveDependencyLoadState::NotLoaded => {
// dependants_waiting_on_rec_load should be None in this case
unreachable!("`Loading` and `NotLoaded` state should never be propagated.")
}
}
}
}
/// Recursively propagates loaded state up the dependency tree.
fn propagate_loaded_state(
infos: &mut AssetInfos,
loaded_id: UntypedAssetId,
waiting_id: UntypedAssetId,
sender: &Sender<InternalAssetEvent>,
) {
let dependants_waiting_on_rec_load = if let Some(info) = infos.get_mut(waiting_id) {
info.loading_rec_dependencies.remove(&loaded_id);
if info.loading_rec_dependencies.is_empty() && info.failed_rec_dependencies.is_empty() {
info.rec_dep_load_state = RecursiveDependencyLoadState::Loaded;
if info.load_state == LoadState::Loaded {
sender
.send(InternalAssetEvent::LoadedWithDependencies { id: waiting_id })
.unwrap();
}
Some(std::mem::take(
&mut info.dependants_waiting_on_recursive_dep_load,
))
} else {
None
}
} else {
None
};
if let Some(dependants_waiting_on_rec_load) = dependants_waiting_on_rec_load {
for dep_id in dependants_waiting_on_rec_load {
Self::propagate_loaded_state(infos, waiting_id, dep_id, sender);
}
}
}
/// Recursively propagates failed state up the dependency tree
fn propagate_failed_state(
infos: &mut AssetInfos,
failed_id: UntypedAssetId,
waiting_id: UntypedAssetId,
error: &Arc<AssetLoadError>,
) {
let dependants_waiting_on_rec_load = if let Some(info) = infos.get_mut(waiting_id) {
info.loading_rec_dependencies.remove(&failed_id);
info.failed_rec_dependencies.insert(failed_id);
info.rec_dep_load_state = RecursiveDependencyLoadState::Failed(error.clone());
Some(std::mem::take(
&mut info.dependants_waiting_on_recursive_dep_load,
))
} else {
None
};
if let Some(dependants_waiting_on_rec_load) = dependants_waiting_on_rec_load {
for dep_id in dependants_waiting_on_rec_load {
Self::propagate_failed_state(infos, waiting_id, dep_id, error);
}
}
}
pub(crate) fn process_asset_fail(&mut self, failed_id: UntypedAssetId, error: AssetLoadError) {
// Check whether the handle has been dropped since the asset was loaded.
if !self.infos.contains_key(&failed_id) {
return;
}
let error = Arc::new(error);
let (dependants_waiting_on_load, dependants_waiting_on_rec_load) = {
let Some(info) = self.get_mut(failed_id) else {
// The asset was already dropped.
return;
};
info.load_state = LoadState::Failed(error.clone());
info.dep_load_state = DependencyLoadState::Failed(error.clone());
info.rec_dep_load_state = RecursiveDependencyLoadState::Failed(error.clone());
(
std::mem::take(&mut info.dependants_waiting_on_load),
std::mem::take(&mut info.dependants_waiting_on_recursive_dep_load),
)
};
for waiting_id in dependants_waiting_on_load {
if let Some(info) = self.get_mut(waiting_id) {
info.loading_dependencies.remove(&failed_id);
info.failed_dependencies.insert(failed_id);
// don't overwrite DependencyLoadState if already failed to preserve first error
if !(matches!(info.dep_load_state, DependencyLoadState::Failed(_))) {
info.dep_load_state = DependencyLoadState::Failed(error.clone());
}
}
}
for waiting_id in dependants_waiting_on_rec_load {
Self::propagate_failed_state(self, failed_id, waiting_id, &error);
}
}
fn remove_dependants_and_labels(
info: &AssetInfo,
loader_dependants: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
path: &AssetPath<'static>,
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<Box<str>>>,
) {
for loader_dependency in info.loader_dependencies.keys() {
if let Some(dependants) = loader_dependants.get_mut(loader_dependency) {
dependants.remove(path);
}
}
let Some(label) = path.label() else {
return;
};
let mut without_label = path.to_owned();
without_label.remove_label();
let Entry::Occupied(mut entry) = living_labeled_assets.entry(without_label) else {
return;
};
entry.get_mut().remove(label);
if entry.get().is_empty() {
entry.remove();
}
}
fn process_handle_drop_internal(
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
path_to_id: &mut HashMap<AssetPath<'static>, TypeIdMap<UntypedAssetId>>,
loader_dependants: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<Box<str>>>,
pending_tasks: &mut HashMap<UntypedAssetId, Task<()>>,
watching_for_changes: bool,
id: UntypedAssetId,
) -> bool {
let Entry::Occupied(mut entry) = infos.entry(id) else {
// Either the asset was already dropped, it doesn't exist, or it isn't managed by the asset server
// None of these cases should result in a removal from the Assets collection
return false;
};
if entry.get_mut().handle_drops_to_skip > 0 {
entry.get_mut().handle_drops_to_skip -= 1;
return false;
}
pending_tasks.remove(&id);
let type_id = entry.key().type_id();
let info = entry.remove();
let Some(path) = &info.path else {
return true;
};
if watching_for_changes {
Self::remove_dependants_and_labels(
&info,
loader_dependants,
path,
living_labeled_assets,
);
}
if let Some(map) = path_to_id.get_mut(path) {
map.remove(&type_id);
if map.is_empty() {
path_to_id.remove(path);
}
};
true
}
/// Consumes all current handle drop events. This will update information in [`AssetInfos`], but it
/// will not affect [`Assets`] storages. For normal use cases, prefer `Assets::track_assets()`
/// This should only be called if `Assets` storage isn't being used (such as in [`AssetProcessor`](crate::processor::AssetProcessor))
///
/// [`Assets`]: crate::Assets
pub(crate) fn consume_handle_drop_events(&mut self) {
for provider in self.handle_providers.values() {
while let Ok(drop_event) = provider.drop_receiver.try_recv() {
let id = drop_event.id;
if drop_event.asset_server_managed {
Self::process_handle_drop_internal(
&mut self.infos,
&mut self.path_to_id,
&mut self.loader_dependants,
&mut self.living_labeled_assets,
&mut self.pending_tasks,
self.watching_for_changes,
id.untyped(provider.type_id),
);
}
}
}
}
}
/// Determines how a handle should be initialized
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) enum HandleLoadingMode {
/// The handle is for an asset that isn't loading/loaded yet.
NotLoading,
/// The handle is for an asset that is being _requested_ to load (if it isn't already loading)
Request,
/// The handle is for an asset that is being forced to load (even if it has already loaded)
Force,
}
#[derive(Error, Debug)]
#[error("Cannot allocate a handle because no handle provider exists for asset type {0:?}")]
pub struct MissingHandleProviderError(TypeId);
/// An error encountered during [`AssetInfos::get_or_create_path_handle_internal`].
#[derive(Error, Debug)]
pub(crate) enum GetOrCreateHandleInternalError {
#[error(transparent)]
MissingHandleProviderError(#[from] MissingHandleProviderError),
#[error("Handle does not exist but TypeId was not specified.")]
HandleMissingButTypeIdNotSpecified,
}
pub(crate) fn unwrap_with_context<T>(
result: Result<T, GetOrCreateHandleInternalError>,
type_name: &'static str,
) -> Option<T> {
match result {
Ok(value) => Some(value),
Err(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified) => None,
Err(GetOrCreateHandleInternalError::MissingHandleProviderError(_)) => {
panic!("Cannot allocate an Asset Handle of type '{type_name}' because the asset type has not been initialized. \
Make sure you have called app.init_asset::<{type_name}>()")
}
}
}