Skip to content

Commit aa1104e

Browse files
authored
Create tests to show that dropping handles can cancel loads. (#21518)
# Objective - Make sure we don't lose the ability to cancel a load when dropping handles. ## Solution - Add tests! ## Testing - Yes!
1 parent b94662a commit aa1104e

File tree

1 file changed

+145
-0
lines changed

1 file changed

+145
-0
lines changed

crates/bevy_asset/src/lib.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2085,6 +2085,151 @@ mod tests {
20852085
);
20862086
}
20872087

2088+
/// A loader that notifies a sender when the loader has started, and blocks on a receiver to
2089+
/// simulate a long asset loader.
2090+
// Note: we can't just use the GatedReader, since currently we hold the handle until after
2091+
// we've selected the reader. The GatedReader blocks this process, so we need to wait until
2092+
// we gate in the loader instead.
2093+
struct GatedLoader {
2094+
in_loader_sender: async_channel::Sender<()>,
2095+
gate_receiver: async_channel::Receiver<()>,
2096+
}
2097+
2098+
impl AssetLoader for GatedLoader {
2099+
type Asset = TestAsset;
2100+
type Error = std::io::Error;
2101+
type Settings = ();
2102+
2103+
async fn load(
2104+
&self,
2105+
_reader: &mut dyn Reader,
2106+
_settings: &Self::Settings,
2107+
_load_context: &mut LoadContext<'_>,
2108+
) -> Result<Self::Asset, Self::Error> {
2109+
self.in_loader_sender.send_blocking(()).unwrap();
2110+
let _ = self.gate_receiver.recv().await;
2111+
Ok(TestAsset)
2112+
}
2113+
2114+
fn extensions(&self) -> &[&str] {
2115+
&["ron"]
2116+
}
2117+
}
2118+
2119+
#[test]
2120+
fn dropping_handle_while_loading_cancels_load() {
2121+
let dir = Dir::default();
2122+
let mut app = App::new();
2123+
let reader = MemoryAssetReader { root: dir.clone() };
2124+
app.register_asset_source(
2125+
AssetSourceId::Default,
2126+
AssetSource::build().with_reader(move || Box::new(reader.clone())),
2127+
)
2128+
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()));
2129+
2130+
let (in_loader_sender, in_loader_receiver) = async_channel::bounded(1);
2131+
let (gate_sender, gate_receiver) = async_channel::bounded(1);
2132+
2133+
app.init_asset::<TestAsset>()
2134+
.register_asset_loader(GatedLoader {
2135+
in_loader_sender,
2136+
gate_receiver,
2137+
});
2138+
2139+
let path = Path::new("abc.ron");
2140+
dir.insert_asset_text(path, "blah");
2141+
2142+
let asset_server = app.world().resource::<AssetServer>().clone();
2143+
2144+
// Start loading the asset. This load will get blocked by the gate.
2145+
let handle = asset_server.load::<TestAsset>(path);
2146+
assert!(asset_server.get_load_state(&handle).unwrap().is_loading());
2147+
app.update();
2148+
2149+
// Make sure we are inside the loader before continuing.
2150+
in_loader_receiver.recv_blocking().unwrap();
2151+
2152+
let asset_id = handle.id();
2153+
// Dropping the handle and doing another update should result in the load being cancelled.
2154+
drop(handle);
2155+
app.update();
2156+
assert!(asset_server.get_load_state(asset_id).is_none());
2157+
2158+
// Unblock the loader and then update a few times, showing that the asset never loads.
2159+
gate_sender.send_blocking(()).unwrap();
2160+
for _ in 0..10 {
2161+
app.update();
2162+
for message in app
2163+
.world()
2164+
.resource::<Messages<AssetEvent<TestAsset>>>()
2165+
.iter_current_update_messages()
2166+
{
2167+
match message {
2168+
AssetEvent::Unused { .. } => {}
2169+
message => panic!("No asset events are allowed: {message:?}"),
2170+
}
2171+
}
2172+
}
2173+
}
2174+
2175+
#[test]
2176+
fn dropping_subasset_handle_while_loading_cancels_load() {
2177+
let dir = Dir::default();
2178+
let mut app = App::new();
2179+
let reader = MemoryAssetReader { root: dir.clone() };
2180+
app.register_asset_source(
2181+
AssetSourceId::Default,
2182+
AssetSource::build().with_reader(move || Box::new(reader.clone())),
2183+
)
2184+
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()));
2185+
2186+
let (in_loader_sender, in_loader_receiver) = async_channel::bounded(1);
2187+
let (gate_sender, gate_receiver) = async_channel::bounded(1);
2188+
2189+
app.init_asset::<TestAsset>()
2190+
.register_asset_loader(GatedLoader {
2191+
in_loader_sender,
2192+
gate_receiver,
2193+
});
2194+
2195+
let path = Path::new("abc.ron");
2196+
dir.insert_asset_text(path, "blah");
2197+
2198+
let asset_server = app.world().resource::<AssetServer>().clone();
2199+
2200+
// Start loading the subasset. This load will get blocked by the gate.
2201+
// Note: it doesn't matter that the subasset doesn't actually end up existing, since the
2202+
// asset system doesn't know that until after the load completes, which we cancel anyway.
2203+
let handle = asset_server.load::<TestAsset>("abc.ron#sub");
2204+
assert!(asset_server.get_load_state(&handle).unwrap().is_loading());
2205+
app.update();
2206+
2207+
// Make sure we are inside the loader before continuing.
2208+
in_loader_receiver.recv_blocking().unwrap();
2209+
2210+
let asset_id = handle.id();
2211+
// Dropping the handle and doing another update should result in the load being cancelled.
2212+
drop(handle);
2213+
app.update();
2214+
assert!(asset_server.get_load_state(asset_id).is_none());
2215+
2216+
// Unblock the loader and then update a few times, showing that the asset never loads.
2217+
gate_sender.send_blocking(()).unwrap();
2218+
for _ in 0..10 {
2219+
app.update();
2220+
for message in app
2221+
.world()
2222+
.resource::<Messages<AssetEvent<TestAsset>>>()
2223+
.iter_current_update_messages()
2224+
{
2225+
match message {
2226+
AssetEvent::Unused { .. } => {}
2227+
message => panic!("No asset events are allowed: {message:?}"),
2228+
}
2229+
}
2230+
}
2231+
}
2232+
20882233
// Creates a basic app with the default asset source engineered to get back the asset event
20892234
// sender.
20902235
fn create_app_with_source_event_sender() -> (App, Dir, Sender<AssetSourceEvent>) {

0 commit comments

Comments
 (0)