Restructure the logic in AssetServer::load_internal
to avoid using weak handles. (#19634)
# Objective - A step towards #19024. - The logic here was kinda complex before. ## Solution - I've restructured the logic here while preserving the behavior (as far as I can tell). - We no longer return the handle if it was passed in. The caller should already have access to it, and the returned handle will be a weak handle, not a strong handle (which can cause issues). This prevents us from needing weak handles at all here. - I verified the callers do not need the return value. The only callsite that needs the returned handle does not pass in the input_handle argument. ## Testing - CI
This commit is contained in:
parent
2ea8f779c3
commit
38058965ef
@ -553,7 +553,9 @@ impl AssetServer {
|
|||||||
path: impl Into<AssetPath<'a>>,
|
path: impl Into<AssetPath<'a>>,
|
||||||
) -> Result<UntypedHandle, AssetLoadError> {
|
) -> Result<UntypedHandle, AssetLoadError> {
|
||||||
let path: AssetPath = path.into();
|
let path: AssetPath = path.into();
|
||||||
self.load_internal(None, path, false, None).await
|
self.load_internal(None, path, false, None)
|
||||||
|
.await
|
||||||
|
.map(|h| h.expect("handle must be returned, since we didn't pass in an input handle"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_unknown_type_with_meta_transform<'a>(
|
pub(crate) fn load_unknown_type_with_meta_transform<'a>(
|
||||||
@ -643,21 +645,25 @@ impl AssetServer {
|
|||||||
|
|
||||||
/// Performs an async asset load.
|
/// Performs an async asset load.
|
||||||
///
|
///
|
||||||
/// `input_handle` must only be [`Some`] if `should_load` was true when retrieving `input_handle`. This is an optimization to
|
/// `input_handle` must only be [`Some`] if `should_load` was true when retrieving
|
||||||
/// avoid looking up `should_load` twice, but it means you _must_ be sure a load is necessary when calling this function with [`Some`].
|
/// `input_handle`. This is an optimization to avoid looking up `should_load` twice, but it
|
||||||
|
/// means you _must_ be sure a load is necessary when calling this function with [`Some`].
|
||||||
|
///
|
||||||
|
/// Returns the handle of the asset if one was retrieved by this function. Otherwise, may return
|
||||||
|
/// [`None`].
|
||||||
async fn load_internal<'a>(
|
async fn load_internal<'a>(
|
||||||
&self,
|
&self,
|
||||||
mut input_handle: Option<UntypedHandle>,
|
input_handle: Option<UntypedHandle>,
|
||||||
path: AssetPath<'a>,
|
path: AssetPath<'a>,
|
||||||
force: bool,
|
force: bool,
|
||||||
meta_transform: Option<MetaTransform>,
|
meta_transform: Option<MetaTransform>,
|
||||||
) -> Result<UntypedHandle, AssetLoadError> {
|
) -> Result<Option<UntypedHandle>, AssetLoadError> {
|
||||||
let asset_type_id = input_handle.as_ref().map(UntypedHandle::type_id);
|
let input_handle_type_id = input_handle.as_ref().map(UntypedHandle::type_id);
|
||||||
|
|
||||||
let path = path.into_owned();
|
let path = path.into_owned();
|
||||||
let path_clone = path.clone();
|
let path_clone = path.clone();
|
||||||
let (mut meta, loader, mut reader) = self
|
let (mut meta, loader, mut reader) = self
|
||||||
.get_meta_loader_and_reader(&path_clone, asset_type_id)
|
.get_meta_loader_and_reader(&path_clone, input_handle_type_id)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| {
|
.inspect_err(|e| {
|
||||||
// if there was an input handle, a "load" operation has already started, so we must produce a "failure" event, if
|
// if there was an input handle, a "load" operation has already started, so we must produce a "failure" event, if
|
||||||
@ -674,76 +680,90 @@ impl AssetServer {
|
|||||||
if let Some(meta_transform) = input_handle.as_ref().and_then(|h| h.meta_transform()) {
|
if let Some(meta_transform) = input_handle.as_ref().and_then(|h| h.meta_transform()) {
|
||||||
(*meta_transform)(&mut *meta);
|
(*meta_transform)(&mut *meta);
|
||||||
}
|
}
|
||||||
// downgrade the input handle so we don't keep the asset alive just because we're loading it
|
|
||||||
// note we can't just pass a weak handle in, as only strong handles contain the asset meta transform
|
|
||||||
input_handle = input_handle.map(|h| h.clone_weak());
|
|
||||||
|
|
||||||
// This contains Some(UntypedHandle), if it was retrievable
|
let asset_id; // The asset ID of the asset we are trying to load.
|
||||||
// If it is None, that is because it was _not_ retrievable, due to
|
let fetched_handle; // The handle if one was looked up/created.
|
||||||
// 1. The handle was not already passed in for this path, meaning we can't just use that
|
let should_load; // Whether we need to load the asset.
|
||||||
// 2. The asset has not been loaded yet, meaning there is no existing Handle for it
|
if let Some(input_handle) = input_handle {
|
||||||
// 3. The path has a label, meaning the AssetLoader's root asset type is not the path's asset type
|
asset_id = Some(input_handle.id());
|
||||||
//
|
// In this case, we intentionally drop the input handle so we can cancel loading the
|
||||||
// In the None case, the only course of action is to wait for the asset to load so we can allocate the
|
// asset if the handle gets dropped (externally) before it finishes loading.
|
||||||
// handle for that type.
|
fetched_handle = None;
|
||||||
//
|
// The handle was passed in, so the "should_load" check was already done.
|
||||||
// TODO: Note that in the None case, multiple asset loads for the same path can happen at the same time
|
should_load = true;
|
||||||
// (rather than "early out-ing" in the "normal" case)
|
} else {
|
||||||
// This would be resolved by a universal asset id, as we would not need to resolve the asset type
|
// TODO: multiple asset loads for the same path can happen at the same time (rather than
|
||||||
// to generate the ID. See this issue: https://github.com/bevyengine/bevy/issues/10549
|
// "early out-ing" in the "normal" case). This would be resolved by a universal asset
|
||||||
let handle_result = match input_handle {
|
// id, as we would not need to resolve the asset type to generate the ID. See this
|
||||||
Some(handle) => {
|
// issue: https://github.com/bevyengine/bevy/issues/10549
|
||||||
// if a handle was passed in, the "should load" check was already done
|
|
||||||
Some((handle, true))
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let mut infos = self.data.infos.write();
|
|
||||||
let result = infos.get_or_create_path_handle_internal(
|
|
||||||
path.clone(),
|
|
||||||
path.label().is_none().then(|| loader.asset_type_id()),
|
|
||||||
HandleLoadingMode::Request,
|
|
||||||
meta_transform,
|
|
||||||
);
|
|
||||||
unwrap_with_context(result, Either::Left(loader.asset_type_name()))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let handle = if let Some((handle, should_load)) = handle_result {
|
let mut infos = self.data.infos.write();
|
||||||
if path.label().is_none() && handle.type_id() != loader.asset_type_id() {
|
let result = infos.get_or_create_path_handle_internal(
|
||||||
|
path.clone(),
|
||||||
|
path.label().is_none().then(|| loader.asset_type_id()),
|
||||||
|
HandleLoadingMode::Request,
|
||||||
|
meta_transform,
|
||||||
|
);
|
||||||
|
match unwrap_with_context(result, Either::Left(loader.asset_type_name())) {
|
||||||
|
// We couldn't figure out the correct handle without its type ID (which can only
|
||||||
|
// happen if we are loading a subasset).
|
||||||
|
None => {
|
||||||
|
// We don't know the expected type since the subasset may have a different type
|
||||||
|
// than the "root" asset (which is the type the loader will load).
|
||||||
|
asset_id = None;
|
||||||
|
fetched_handle = None;
|
||||||
|
// If we couldn't find an appropriate handle, then the asset certainly needs to
|
||||||
|
// be loaded.
|
||||||
|
should_load = true;
|
||||||
|
}
|
||||||
|
Some((handle, result_should_load)) => {
|
||||||
|
asset_id = Some(handle.id());
|
||||||
|
fetched_handle = Some(handle);
|
||||||
|
should_load = result_should_load;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Verify that the expected type matches the loader's type.
|
||||||
|
if let Some(asset_type_id) = asset_id.map(|id| id.type_id()) {
|
||||||
|
// If we are loading a subasset, then the subasset's type almost certainly doesn't match
|
||||||
|
// the loader's type - and that's ok.
|
||||||
|
if path.label().is_none() && asset_type_id != loader.asset_type_id() {
|
||||||
error!(
|
error!(
|
||||||
"Expected {:?}, got {:?}",
|
"Expected {:?}, got {:?}",
|
||||||
handle.type_id(),
|
asset_type_id,
|
||||||
loader.asset_type_id()
|
loader.asset_type_id()
|
||||||
);
|
);
|
||||||
return Err(AssetLoadError::RequestedHandleTypeMismatch {
|
return Err(AssetLoadError::RequestedHandleTypeMismatch {
|
||||||
path: path.into_owned(),
|
path: path.into_owned(),
|
||||||
requested: handle.type_id(),
|
requested: asset_type_id,
|
||||||
actual_asset_name: loader.asset_type_name(),
|
actual_asset_name: loader.asset_type_name(),
|
||||||
loader_name: loader.type_name(),
|
loader_name: loader.type_name(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if !should_load && !force {
|
}
|
||||||
return Ok(handle);
|
// Bail out earlier if we don't need to load the asset.
|
||||||
}
|
if !should_load && !force {
|
||||||
Some(handle)
|
return Ok(fetched_handle);
|
||||||
} else {
|
}
|
||||||
None
|
|
||||||
};
|
|
||||||
// if the handle result is None, we definitely need to load the asset
|
|
||||||
|
|
||||||
let (base_handle, base_path) = if path.label().is_some() {
|
// We don't actually need to use _base_handle, but we do need to keep the handle alive.
|
||||||
|
// Dropping it would cancel the load of the base asset, which would make the load of this
|
||||||
|
// subasset never complete.
|
||||||
|
let (base_asset_id, _base_handle, base_path) = if path.label().is_some() {
|
||||||
let mut infos = self.data.infos.write();
|
let mut infos = self.data.infos.write();
|
||||||
let base_path = path.without_label().into_owned();
|
let base_path = path.without_label().into_owned();
|
||||||
let (base_handle, _) = infos.get_or_create_path_handle_erased(
|
let base_handle = infos
|
||||||
base_path.clone(),
|
.get_or_create_path_handle_erased(
|
||||||
loader.asset_type_id(),
|
base_path.clone(),
|
||||||
Some(loader.asset_type_name()),
|
loader.asset_type_id(),
|
||||||
HandleLoadingMode::Force,
|
Some(loader.asset_type_name()),
|
||||||
None,
|
HandleLoadingMode::Force,
|
||||||
);
|
None,
|
||||||
(base_handle, base_path)
|
)
|
||||||
|
.0;
|
||||||
|
(base_handle.id(), Some(base_handle), base_path)
|
||||||
} else {
|
} else {
|
||||||
(handle.clone().unwrap(), path.clone())
|
(asset_id.unwrap(), None, path.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
match self
|
match self
|
||||||
@ -760,7 +780,7 @@ impl AssetServer {
|
|||||||
Ok(loaded_asset) => {
|
Ok(loaded_asset) => {
|
||||||
let final_handle = if let Some(label) = path.label_cow() {
|
let final_handle = if let Some(label) = path.label_cow() {
|
||||||
match loaded_asset.labeled_assets.get(&label) {
|
match loaded_asset.labeled_assets.get(&label) {
|
||||||
Some(labeled_asset) => labeled_asset.handle.clone(),
|
Some(labeled_asset) => Some(labeled_asset.handle.clone()),
|
||||||
None => {
|
None => {
|
||||||
let mut all_labels: Vec<String> = loaded_asset
|
let mut all_labels: Vec<String> = loaded_asset
|
||||||
.labeled_assets
|
.labeled_assets
|
||||||
@ -776,16 +796,15 @@ impl AssetServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if the path does not have a label, the handle must exist at this point
|
fetched_handle
|
||||||
handle.unwrap()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.send_loaded_asset(base_handle.id(), loaded_asset);
|
self.send_loaded_asset(base_asset_id, loaded_asset);
|
||||||
Ok(final_handle)
|
Ok(final_handle)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.send_asset_event(InternalAssetEvent::Failed {
|
self.send_asset_event(InternalAssetEvent::Failed {
|
||||||
id: base_handle.id(),
|
id: base_asset_id,
|
||||||
error: err.clone(),
|
error: err.clone(),
|
||||||
path: path.into_owned(),
|
path: path.into_owned(),
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user