@@ -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