Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
382 changes: 382 additions & 0 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1967,6 +1967,388 @@ mod tests {
});
}

#[test]
fn error_on_immediate_load_of_self_path() {
let (mut app, dir, _source_events) = create_app_with_source_event_sender();
let asset_server = app.world().resource::<AssetServer>().clone();

// Extension "rsp" for Recursive Self Path (RSP).
dir.insert_asset_text(Path::new("abc.rsp"), "");

struct ImmediateSelfLoader;

impl AssetLoader for ImmediateSelfLoader {
type Asset = TestAsset;
type Error = crate::loader::LoadDirectError;
type Settings = ();

async fn load(
&self,
_: &mut dyn Reader,
_: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let asset_path = load_context.path().clone();
let loaded_asset = load_context
.loader()
.immediate()
.load::<TestAsset>(asset_path)
.await?;
load_context.add_loaded_labeled_asset("myself".to_string(), loaded_asset);
Ok(TestAsset)
}

fn extensions(&self) -> &[&str] {
&["rsp"]
}
}

app.init_asset::<TestAsset>()
.register_asset_loader(ImmediateSelfLoader);

let handle: Handle<TestAsset> = asset_server.load("abc.rsp");

run_app_until(&mut app, |_world| match asset_server.load_state(&handle) {
LoadState::Loading => None,
LoadState::Failed(err) => {
assert!(
format!("{:?}", &err).contains("RequestedSelfPath"),
"Error did not contain RequestedSelfPath: {:?}",
&err
);
Some(())
}
state => panic!("Unexpected asset state: {state:?}"),
});
}

#[test]
fn error_on_unknown_type_immediate_load_of_self_path() {
let (mut app, dir, _source_events) = create_app_with_source_event_sender();
let asset_server = app.world().resource::<AssetServer>().clone();

dir.insert_asset_text(Path::new("abc.rsp"), "");

struct ImmediateSelfLoader;

impl AssetLoader for ImmediateSelfLoader {
type Asset = TestAsset;
type Error = crate::loader::LoadDirectError;
type Settings = ();

async fn load(
&self,
_: &mut dyn Reader,
_: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let asset_path = load_context.path().clone();
let loaded_asset = load_context
.loader()
.immediate()
.with_unknown_type()
.load(asset_path)
.await?;
let Ok(loaded_asset) = loaded_asset.downcast::<TestAsset>() else {
panic!("Could not downcast to `TestAsset`")
};
load_context.add_loaded_labeled_asset("myself".to_string(), loaded_asset);
Ok(TestAsset)
}

fn extensions(&self) -> &[&str] {
&["rsp"]
}
}

app.init_asset::<TestAsset>()
.register_asset_loader(ImmediateSelfLoader);

let handle: Handle<TestAsset> = asset_server.load("abc.rsp");

run_app_until(&mut app, |_world| match asset_server.load_state(&handle) {
LoadState::Loading => None,
LoadState::Failed(err) => {
assert!(
format!("{:?}", &err).contains("RequestedSelfPath"),
"Error did not contain RequestedSelfPath: {:?}",
&err
);
Some(())
}
state => panic!("Unexpected asset state: {state:?}"),
});
}

/// This is not a statement of intent but of behavior: One may load their
/// own path deferred without error. It has the correct handle to itself.
/// And it can reload.
#[test]
fn no_error_on_deferred_load_of_self_path() {
let (mut app, dir, source_events) = create_app_with_source_event_sender();
let asset_server = app.world().resource::<AssetServer>().clone();

dir.insert_asset_text(Path::new("abc.rsp"), "");

#[derive(Asset, TypePath)]
pub struct TestAsset(Handle<TestAsset>);
struct DeferredSelfLoader;

impl AssetLoader for DeferredSelfLoader {
type Asset = TestAsset;
type Error = crate::loader::LoadDirectError;
type Settings = ();

async fn load(
&self,
_: &mut dyn Reader,
_: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let asset_path = load_context.path().clone();
let loaded_asset = load_context.load::<TestAsset>(asset_path);
Ok(TestAsset(loaded_asset))
}

fn extensions(&self) -> &[&str] {
&["rsp"]
}
}

app.init_asset::<TestAsset>()
.register_asset_loader(DeferredSelfLoader);

let handle: Handle<TestAsset> = asset_server.load("abc.rsp");

run_app_until(&mut app, |world| match asset_server.load_state(&handle) {
LoadState::Loading => None,
LoadState::Loaded => {
let test_assets = world.resource::<Assets<TestAsset>>();
let asset = test_assets.get(&handle).unwrap();
assert_eq!(handle, asset.0);
Some(())
}
state => panic!("Unexpected asset state: {state:?}"),
});

run_app_until(&mut app, |world| {
let messages = collect_asset_events(world);
if messages.is_empty() {
return None;
}
assert_eq!(
messages,
[
AssetEvent::LoadedWithDependencies { id: handle.id() },
AssetEvent::Added { id: handle.id() }
]
);
Some(())
});

// Sending an asset event should result in the asset being reloaded - resulting in a
// "Modified" message.
source_events
.send_blocking(AssetSourceEvent::ModifiedAsset(PathBuf::from("abc.rsp")))
.unwrap();

run_app_until(&mut app, |world| {
let messages = collect_asset_events(world);
if messages.is_empty() {
return None;
}
assert_eq!(
messages,
[
AssetEvent::LoadedWithDependencies { id: handle.id() },
AssetEvent::Modified { id: handle.id() }
]
);
Some(())
});
}

#[test]
fn no_error_on_read_bytes_of_self_path() {
let (mut app, dir, source_events) = create_app_with_source_event_sender();
let asset_server = app.world().resource::<AssetServer>().clone();

dir.insert_asset_text(Path::new("abc.rsp"), "");

struct ReadBytesSelfLoader;

impl AssetLoader for ReadBytesSelfLoader {
type Asset = TestAsset;
type Error = crate::loader::LoadDirectError;
type Settings = ();

async fn load(
&self,
_: &mut dyn Reader,
_: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let asset_path = load_context.path().clone();
let _bytes = load_context.read_asset_bytes(asset_path).await.unwrap();
Ok(TestAsset)
}

fn extensions(&self) -> &[&str] {
&["rsp"]
}
}

app.init_asset::<TestAsset>()
.register_asset_loader(ReadBytesSelfLoader);

let handle: Handle<TestAsset> = asset_server.load("abc.rsp");

run_app_until(&mut app, |_world| match asset_server.load_state(&handle) {
LoadState::Loading => None,
LoadState::Loaded => Some(()),
state => panic!("Unexpected asset state: {state:?}"),
});

run_app_until(&mut app, |world| {
let messages = collect_asset_events(world);
if messages.is_empty() {
return None;
}
assert_eq!(
messages,
[
AssetEvent::LoadedWithDependencies { id: handle.id() },
AssetEvent::Added { id: handle.id() }
]
);
Some(())
});

// Sending an asset event should result in the asset being reloaded - resulting in a
// "Modified" message.
source_events
.send_blocking(AssetSourceEvent::ModifiedAsset(PathBuf::from("abc.rsp")))
.unwrap();

run_app_until(&mut app, |world| {
let messages = collect_asset_events(world);
if messages.is_empty() {
return None;
}
assert_eq!(
messages,
[
AssetEvent::LoadedWithDependencies { id: handle.id() },
AssetEvent::Modified { id: handle.id() }
]
);
Some(())
});
}

/// This is not a statement of intent but of behavior: One may load
/// their self path deferred of unknown type without error. It has the same
/// asset index as the original handle, but not the same type. And it can
/// reload.
///
/// Caveat: Windows behaves strangely on this test. It loads a self-path
/// deferred with an unknown type, but once that path is loaded, it cannot
/// be retrieved from the `Assets<T>` resource.
#[test]
fn no_error_on_unknown_type_deferred_load_of_self_path() {
let (mut app, dir, source_events) = create_app_with_source_event_sender();
let asset_server = app.world().resource::<AssetServer>().clone();

dir.insert_asset_text(Path::new("abc.rsp"), "");

#[derive(Asset, TypePath)]
pub struct TestAssetUD(Handle<crate::LoadedUntypedAsset>);
struct ImmediateSelfLoader;

impl AssetLoader for ImmediateSelfLoader {
type Asset = TestAssetUD;
type Error = crate::loader::LoadDirectError;
type Settings = ();

async fn load(
&self,
_: &mut dyn Reader,
_: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let asset_path = load_context.path().clone();
let untyped_handle: Handle<crate::LoadedUntypedAsset> =
load_context.loader().with_unknown_type().load(asset_path);

Ok(TestAssetUD(untyped_handle))
}

fn extensions(&self) -> &[&str] {
&["rsp"]
}
}

app.init_asset::<TestAssetUD>()
.register_asset_loader(ImmediateSelfLoader);

let handle: Handle<TestAssetUD> = asset_server.load("abc.rsp");

run_app_until(&mut app, |world| match asset_server.load_state(&handle) {
LoadState::Loading => None,
LoadState::Loaded => {
let asset_id = {
let test_assets = world.resource::<Assets<TestAssetUD>>();
let asset = test_assets.get(&handle).unwrap();
asset.0.id()
};
let untyped = world.resource::<Assets<crate::LoadedUntypedAsset>>();
if let Some(untyped_handle) = untyped.get(asset_id) {
assert_eq!(handle.id(), untyped_handle.handle.id());
Some(())
} else {
None
}
}
state => panic!("Unexpected asset state: {state:?}"),
});

run_app_until(&mut app, |world| {
let messages = collect_asset_events(world);
if messages.is_empty() {
return None;
}
assert_eq!(
messages,
[
AssetEvent::LoadedWithDependencies { id: handle.id() },
AssetEvent::Added { id: handle.id() }
]
);
Some(())
});

// Sending an asset event should result in the asset being reloaded - resulting in a
// "Modified" message.
source_events
.send_blocking(AssetSourceEvent::ModifiedAsset(PathBuf::from("abc.rsp")))
.unwrap();

run_app_until(&mut app, |world| {
let messages = collect_asset_events(world);
if messages.is_empty() {
return None;
}
assert_eq!(
messages,
[
AssetEvent::LoadedWithDependencies { id: handle.id() },
AssetEvent::Modified { id: handle.id() }
]
);
Some(())
});
}

// validate the Asset derive macro for various asset types
#[derive(Asset, TypePath)]
pub struct TestAsset;
Expand Down
Loading