Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/bevy_asset/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,14 @@ pub enum UntypedHandle {
}

impl UntypedHandle {
/// Returns the equivalent of [`Handle`]'s default implementation for the given type ID.
pub fn default_for_type(type_id: TypeId) -> Self {
Self::Uuid {
type_id,
uuid: AssetId::<()>::DEFAULT_UUID,
}
}

/// Returns the [`UntypedAssetId`] for the referenced asset.
#[inline]
pub fn id(&self) -> UntypedAssetId {
Expand Down
60 changes: 57 additions & 3 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,7 @@ mod tests {
sync::Mutex,
};
use bevy_reflect::{Reflect, TypePath};
use bevy_tasks::block_on;
use core::{any::TypeId, time::Duration};
use futures_lite::AsyncReadExt;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -2801,8 +2802,7 @@ mod tests {
source.insert_asset_text(Path::new(ASSET_PATH), "blah");

let asset_server = app.world().resource::<AssetServer>().clone();
bevy_tasks::block_on(asset_server.write_default_loader_meta_file_for_path(ASSET_PATH))
.unwrap();
block_on(asset_server.write_default_loader_meta_file_for_path(ASSET_PATH)).unwrap();

assert_eq!(
read_meta_as_string(&source, Path::new(ASSET_PATH)),
Expand All @@ -2829,7 +2829,7 @@ mod tests {

let asset_server = app.world().resource::<AssetServer>().clone();
assert!(matches!(
bevy_tasks::block_on(asset_server.write_default_loader_meta_file_for_path(ASSET_PATH)),
block_on(asset_server.write_default_loader_meta_file_for_path(ASSET_PATH)),
Err(WriteDefaultMetaError::MetaAlreadyExists)
));

Expand Down Expand Up @@ -3083,4 +3083,58 @@ mod tests {
TestLoadState::Failed(TestAssetLoadError::MissingAssetLoader),
);
}

#[test]
fn load_empty_path_returns_default() {
let mut app = create_app().0;

// Not necessary but better to make things more realistic to ensure we hit the right error
// case.
app.init_asset::<TestAsset>()
.register_asset_loader(TrivialLoader);

const TYPE_ID: TypeId = TypeId::of::<TestAsset>();

fn boring_settings(_: &mut ()) {}

let asset_server = app.world().resource::<AssetServer>().clone();

for path in ["", "no_path://#WithALabel"] {
// TODO: We have way too many "load" variants. We **need** to simplify this.
assert_eq!(asset_server.load(path), Handle::<TestAsset>::default());
assert_eq!(
asset_server.load_acquire(path, ()),
Handle::<TestAsset>::default()
);
assert_eq!(
asset_server.load_acquire_override(path, ()),
Handle::<TestAsset>::default()
);
assert_eq!(
asset_server.load_acquire_with_settings(path, boring_settings, ()),
Handle::<TestAsset>::default()
);
assert_eq!(
asset_server.load_erased(TYPE_ID, path),
Handle::<TestAsset>::default()
);
assert_eq!(
asset_server.load_override(path),
Handle::<TestAsset>::default()
);
assert_eq!(asset_server.load_untyped(path), Handle::default());
assert!(matches!(
block_on(asset_server.load_untyped_async(path)),
Err(AssetLoadError::EmptyPath(reported_path)) if AssetPath::from(path) == reported_path
));
assert_eq!(
asset_server.load_with_settings(path, |_: &mut ()| {}),
Handle::<TestAsset>::default()
);
assert_eq!(
asset_server.load_with_settings_override(path, |_: &mut ()| {}),
Handle::<TestAsset>::default()
);
}
}
}
12 changes: 11 additions & 1 deletion crates/bevy_asset/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ use core::any::{Any, TypeId};
use downcast_rs::{impl_downcast, Downcast};
use ron::error::SpannedError;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use thiserror::Error;
use tracing::error;

/// Loads an [`Asset`] from a given byte [`Reader`]. This can accept [`AssetLoader::Settings`], which configure how the [`Asset`]
/// should be loaded.
Expand Down Expand Up @@ -339,6 +340,8 @@ impl<A: Asset> AssetContainer for A {
/// [immediately]: crate::Immediate
#[derive(Error, Debug)]
pub enum LoadDirectError {
#[error("Attempted to load an asset with an empty path \"{0}\"")]
EmptyPath(AssetPath<'static>),
#[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")]
RequestedSubasset(AssetPath<'static>),
#[error("Failed to load dependency {dependency:?} {error}")]
Expand Down Expand Up @@ -566,6 +569,11 @@ impl<'a> LoadContext<'a> {
path: impl Into<AssetPath<'c>>,
) -> Result<Vec<u8>, ReadAssetBytesError> {
let path = path.into();
if path.path() == Path::new("") {
error!("Attempted to load an asset with an empty path \"{path}\"!");
return Err(ReadAssetBytesError::EmptyPath(path.into_owned()));
}

let source = self.asset_server.get_source(path.source())?;
let asset_reader = match self.asset_server.mode() {
AssetServerMode::Unprocessed => source.reader(),
Expand Down Expand Up @@ -679,6 +687,8 @@ impl<'a> LoadContext<'a> {
/// An error produced when calling [`LoadContext::read_asset_bytes`]
#[derive(Error, Debug)]
pub enum ReadAssetBytesError {
#[error("Attempted to load an asset with an empty path \"{0}\"")]
EmptyPath(AssetPath<'static>),
#[error(transparent)]
DeserializeMetaError(#[from] DeserializeMetaError),
#[error(transparent)]
Expand Down
18 changes: 18 additions & 0 deletions crates/bevy_asset/src/loader_builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::{
};
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc};
use core::any::TypeId;
use std::path::Path;
use tracing::error;

// Utility type for handling the sources of reader references
enum ReaderRef<'a> {
Expand Down Expand Up @@ -304,6 +306,10 @@ impl NestedLoader<'_, '_, StaticTyped, Deferred> {
/// [`with_unknown_type`]: Self::with_unknown_type
pub fn load<'c, A: Asset>(self, path: impl Into<AssetPath<'c>>) -> Handle<A> {
let path = path.into().to_owned();
if path.path() == Path::new("") {
error!("Attempted to load an asset with an empty path \"{path}\"!");
return Handle::default();
}
let handle = if self.load_context.should_load_dependencies {
self.load_context.asset_server.load_with_meta_transform(
path,
Expand Down Expand Up @@ -334,6 +340,10 @@ impl NestedLoader<'_, '_, DynamicTyped, Deferred> {
/// [`with_dynamic_type`]: Self::with_dynamic_type
pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> UntypedHandle {
let path = path.into().to_owned();
if path.path() == Path::new("") {
error!("Attempted to load an asset with an empty path \"{path}\"!");
return UntypedHandle::default_for_type(self.typing.asset_type_id);
}
let handle = if self.load_context.should_load_dependencies {
self.load_context
.asset_server
Expand Down Expand Up @@ -367,6 +377,10 @@ impl NestedLoader<'_, '_, UnknownTyped, Deferred> {
/// This will infer the asset type from metadata.
pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> Handle<LoadedUntypedAsset> {
let path = path.into().to_owned();
if path.path() == Path::new("") {
error!("Attempted to load an asset with an empty path \"{path}\"!");
return Handle::default();
}
let handle = if self.load_context.should_load_dependencies {
self.load_context
.asset_server
Expand Down Expand Up @@ -399,6 +413,10 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>>
path: &AssetPath<'static>,
asset_type_id: Option<TypeId>,
) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {
if path.path() == Path::new("") {
error!("Attempted to load an asset with an empty path \"{path}\"!");
return Err(LoadDirectError::EmptyPath(path.clone_owned()));
}
if path.label().is_some() {
return Err(LoadDirectError::RequestedSubasset(path.clone()));
}
Expand Down
20 changes: 20 additions & 0 deletions crates/bevy_asset/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,10 @@ impl AssetServer {
override_unapproved: bool,
) -> Handle<A> {
let path = path.into().into_owned();
if path.path() == Path::new("") {
error!("Attempted to load an asset with an empty path \"{path}\"!");
return Handle::default();
}

if path.is_unapproved() {
match (&self.data.unapproved_path_mode, override_unapproved) {
Expand Down Expand Up @@ -541,6 +545,11 @@ impl AssetServer {
guard: G,
) -> UntypedHandle {
let path = path.into().into_owned();
if path.path() == Path::new("") {
error!("Attempted to load an asset with an empty path \"{path}\"!");
return UntypedHandle::default_for_type(type_id);
}

let mut infos = self.write_infos();
let (handle, should_load) = infos.get_or_create_path_handle_erased(
path.clone(),
Expand Down Expand Up @@ -605,6 +614,10 @@ impl AssetServer {
self.write_infos().stats.started_load_tasks += 1;

let path: AssetPath = path.into();
if path.path() == Path::new("") {
return Err(AssetLoadError::EmptyPath(path.clone_owned()));
}

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"))
Expand All @@ -616,6 +629,11 @@ impl AssetServer {
meta_transform: Option<MetaTransform>,
) -> Handle<LoadedUntypedAsset> {
let path = path.into().into_owned();
if path.path() == Path::new("") {
error!("Attempted to load an asset with an empty path \"{path}\"!");
return Handle::default();
}

let untyped_source = AssetSourceId::Name(match path.source() {
AssetSourceId::Default => CowArc::Static(UNTYPED_SOURCE_SUFFIX),
AssetSourceId::Name(source) => {
Expand Down Expand Up @@ -2083,6 +2101,8 @@ impl RecursiveDependencyLoadState {
reason = "Adding docs to the variants would not add information beyond the error message and the names"
)]
pub enum AssetLoadError {
#[error("Attempted to load an asset with an empty path \"{0}\"")]
EmptyPath(AssetPath<'static>),
#[error("Requested handle of type {requested:?} for asset '{path}' does not match actual asset type '{actual_asset_name}', which used loader '{loader_name}'")]
RequestedHandleTypeMismatch {
path: AssetPath<'static>,
Expand Down