@@ -711,14 +711,16 @@ mod tests {
711
711
handle:: Handle ,
712
712
io:: {
713
713
gated:: { GateOpener , GatedReader } ,
714
- memory:: { Dir , MemoryAssetReader } ,
714
+ memory:: { Dir , MemoryAssetReader , MemoryAssetWriter } ,
715
715
AssetReader , AssetReaderError , AssetSource , AssetSourceEvent , AssetSourceId ,
716
716
AssetWatcher , Reader ,
717
717
} ,
718
718
loader:: { AssetLoader , LoadContext } ,
719
- Asset , AssetApp , AssetEvent , AssetId , AssetLoadError , AssetLoadFailedEvent , AssetPath ,
720
- AssetPlugin , AssetServer , Assets , InvalidGenerationError , LoadState , UnapprovedPathMode ,
721
- UntypedHandle ,
719
+ saver:: AssetSaver ,
720
+ transformer:: { AssetTransformer , TransformedAsset } ,
721
+ Asset , AssetApp , AssetEvent , AssetId , AssetLoadError , AssetLoadFailedEvent , AssetMode ,
722
+ AssetPath , AssetPlugin , AssetServer , Assets , InvalidGenerationError , LoadState ,
723
+ UnapprovedPathMode , UntypedHandle ,
722
724
} ;
723
725
use alloc:: {
724
726
boxed:: Box ,
@@ -739,8 +741,9 @@ mod tests {
739
741
sync:: Mutex ,
740
742
} ;
741
743
use bevy_reflect:: TypePath ;
742
- use core:: time:: Duration ;
744
+ use core:: { marker :: PhantomData , time:: Duration } ;
743
745
use crossbeam_channel:: Sender ;
746
+ use futures_lite:: AsyncWriteExt ;
744
747
use serde:: { Deserialize , Serialize } ;
745
748
use std:: path:: { Path , PathBuf } ;
746
749
use thiserror:: Error ;
@@ -2238,4 +2241,314 @@ mod tests {
2238
2241
Some ( ( ) )
2239
2242
} ) ;
2240
2243
}
2244
+
2245
+ #[ expect( clippy:: allow_attributes, reason = "this is only sometimes unused" ) ]
2246
+ #[ allow(
2247
+ unused,
2248
+ reason = "We only use this for asset processor tests, which are only compiled with the `multi_threaded` feature."
2249
+ ) ]
2250
+ struct AppWithProcessor {
2251
+ app : App ,
2252
+ source_dir : Dir ,
2253
+ processed_dir : Dir ,
2254
+ }
2255
+
2256
+ #[ expect( clippy:: allow_attributes, reason = "this is only sometimes unused" ) ]
2257
+ #[ allow(
2258
+ unused,
2259
+ reason = "We only use this for asset processor tests, which are only compiled with the `multi_threaded` feature."
2260
+ ) ]
2261
+ fn create_app_with_asset_processor ( ) -> AppWithProcessor {
2262
+ let mut app = App :: new ( ) ;
2263
+ let source_dir = Dir :: default ( ) ;
2264
+ let processed_dir = Dir :: default ( ) ;
2265
+
2266
+ let source_memory_reader = MemoryAssetReader {
2267
+ root : source_dir. clone ( ) ,
2268
+ } ;
2269
+ let processed_memory_reader = MemoryAssetReader {
2270
+ root : processed_dir. clone ( ) ,
2271
+ } ;
2272
+ let processed_memory_writer = MemoryAssetWriter {
2273
+ root : processed_dir. clone ( ) ,
2274
+ } ;
2275
+
2276
+ app. register_asset_source (
2277
+ AssetSourceId :: Default ,
2278
+ AssetSource :: build ( )
2279
+ . with_reader ( move || Box :: new ( source_memory_reader. clone ( ) ) )
2280
+ . with_processed_reader ( move || Box :: new ( processed_memory_reader. clone ( ) ) )
2281
+ . with_processed_writer ( move |_| Some ( Box :: new ( processed_memory_writer. clone ( ) ) ) ) ,
2282
+ )
2283
+ . add_plugins ( (
2284
+ TaskPoolPlugin :: default ( ) ,
2285
+ AssetPlugin {
2286
+ mode : AssetMode :: Processed ,
2287
+ use_asset_processor_override : Some ( true ) ,
2288
+ ..Default :: default ( )
2289
+ } ,
2290
+ ) ) ;
2291
+
2292
+ AppWithProcessor {
2293
+ app,
2294
+ source_dir,
2295
+ processed_dir,
2296
+ }
2297
+ }
2298
+
2299
+ #[ expect( clippy:: allow_attributes, reason = "this is only sometimes unused" ) ]
2300
+ #[ allow(
2301
+ unused,
2302
+ reason = "We only use this for asset processor tests, which are only compiled with the `multi_threaded` feature."
2303
+ ) ]
2304
+ struct CoolTextSaver ;
2305
+
2306
+ impl AssetSaver for CoolTextSaver {
2307
+ type Asset = CoolText ;
2308
+ type Settings = ( ) ;
2309
+ type OutputLoader = CoolTextLoader ;
2310
+ type Error = std:: io:: Error ;
2311
+
2312
+ async fn save (
2313
+ & self ,
2314
+ writer : & mut crate :: io:: Writer ,
2315
+ asset : crate :: saver:: SavedAsset < ' _ , Self :: Asset > ,
2316
+ _: & Self :: Settings ,
2317
+ ) -> Result < ( ) , Self :: Error > {
2318
+ let ron = CoolTextRon {
2319
+ text : asset. text . clone ( ) ,
2320
+ sub_texts : asset
2321
+ . iter_labels ( )
2322
+ . map ( |label| asset. get_labeled :: < SubText , _ > ( label) . unwrap ( ) . text . clone ( ) )
2323
+ . collect ( ) ,
2324
+ dependencies : asset
2325
+ . dependencies
2326
+ . iter ( )
2327
+ . map ( |handle| handle. path ( ) . unwrap ( ) . path ( ) )
2328
+ . map ( |path| path. to_str ( ) . unwrap ( ) . to_string ( ) )
2329
+ . collect ( ) ,
2330
+ // NOTE: We can't handle embedded dependencies in any way, since we need to write to
2331
+ // another file to do so.
2332
+ embedded_dependencies : vec ! [ ] ,
2333
+ } ;
2334
+ let ron = ron:: ser:: to_string ( & ron) . unwrap ( ) ;
2335
+ writer. write_all ( ron. as_bytes ( ) ) . await ?;
2336
+ Ok ( ( ) )
2337
+ }
2338
+ }
2339
+
2340
+ #[ expect( clippy:: allow_attributes, reason = "this is only sometimes unused" ) ]
2341
+ #[ allow(
2342
+ unused,
2343
+ reason = "We only use this for asset processor tests, which are only compiled with the `multi_threaded` feature."
2344
+ ) ]
2345
+ // Note: while we allow any Fn, since closures are unnameable types, creating a processor with a
2346
+ // closure cannot be used (since we need to include the name of the transformer in the meta
2347
+ // file).
2348
+ struct RootAssetTransformer < M : MutateAsset < A > , A : Asset > ( M , PhantomData < fn ( & mut A ) > ) ;
2349
+
2350
+ trait MutateAsset < A : Asset > : Send + Sync + ' static {
2351
+ fn mutate ( & self , asset : & mut A ) ;
2352
+ }
2353
+
2354
+ impl < M : MutateAsset < A > , A : Asset > RootAssetTransformer < M , A > {
2355
+ #[ allow(
2356
+ unused,
2357
+ reason = "We only use this for asset processor tests, which are only compiled with the `multi_threaded` feature."
2358
+ ) ]
2359
+ fn new ( m : M ) -> Self {
2360
+ Self ( m, PhantomData )
2361
+ }
2362
+ }
2363
+
2364
+ impl < M : MutateAsset < A > , A : Asset > AssetTransformer for RootAssetTransformer < M , A > {
2365
+ type AssetInput = A ;
2366
+ type AssetOutput = A ;
2367
+ type Error = std:: io:: Error ;
2368
+ type Settings = ( ) ;
2369
+
2370
+ async fn transform < ' a > (
2371
+ & ' a self ,
2372
+ mut asset : TransformedAsset < A > ,
2373
+ _settings : & ' a Self :: Settings ,
2374
+ ) -> Result < TransformedAsset < A > , Self :: Error > {
2375
+ self . 0 . mutate ( asset. get_mut ( ) ) ;
2376
+ Ok ( asset)
2377
+ }
2378
+ }
2379
+
2380
+ #[ cfg( feature = "multi_threaded" ) ]
2381
+ use crate :: processor:: { AssetProcessor , LoadTransformAndSave } ;
2382
+
2383
+ // The asset processor currently requires multi_threaded.
2384
+ #[ cfg( feature = "multi_threaded" ) ]
2385
+ #[ test]
2386
+ fn no_meta_or_default_processor_copies_asset ( ) {
2387
+ // Assets without a meta file or a default processor should still be accessible in the
2388
+ // processed path. Note: This isn't exactly the desired property - we don't want the assets
2389
+ // to be copied to the processed directory. We just want these assets to still be loadable
2390
+ // if we no longer have the source directory. This could be done with a symlink instead of a
2391
+ // copy.
2392
+
2393
+ let AppWithProcessor {
2394
+ mut app,
2395
+ source_dir,
2396
+ processed_dir,
2397
+ } = create_app_with_asset_processor ( ) ;
2398
+
2399
+ let path = Path :: new ( "abc.cool.ron" ) ;
2400
+ let source_asset = r#"(
2401
+ text: "abc",
2402
+ dependencies: [],
2403
+ embedded_dependencies: [],
2404
+ sub_texts: [],
2405
+ )"# ;
2406
+
2407
+ source_dir. insert_asset_text ( path, source_asset) ;
2408
+
2409
+ // Start the app, which also starts the asset processor.
2410
+ app. update ( ) ;
2411
+
2412
+ // Wait for all processing to finish.
2413
+ bevy_tasks:: block_on (
2414
+ app. world ( )
2415
+ . resource :: < AssetProcessor > ( )
2416
+ . data ( )
2417
+ . wait_until_finished ( ) ,
2418
+ ) ;
2419
+
2420
+ let processed_asset = processed_dir. get_asset ( path) . unwrap ( ) ;
2421
+ let processed_asset = str:: from_utf8 ( processed_asset. value ( ) ) . unwrap ( ) ;
2422
+ assert_eq ! ( processed_asset, source_asset) ;
2423
+ }
2424
+
2425
+ // The asset processor currently requires multi_threaded.
2426
+ #[ cfg( feature = "multi_threaded" ) ]
2427
+ #[ test]
2428
+ fn asset_processor_transforms_asset_default_processor ( ) {
2429
+ let AppWithProcessor {
2430
+ mut app,
2431
+ source_dir,
2432
+ processed_dir,
2433
+ } = create_app_with_asset_processor ( ) ;
2434
+
2435
+ struct AddText ;
2436
+
2437
+ impl MutateAsset < CoolText > for AddText {
2438
+ fn mutate ( & self , text : & mut CoolText ) {
2439
+ text. text . push_str ( "_def" ) ;
2440
+ }
2441
+ }
2442
+
2443
+ type CoolTextProcessor = LoadTransformAndSave <
2444
+ CoolTextLoader ,
2445
+ RootAssetTransformer < AddText , CoolText > ,
2446
+ CoolTextSaver ,
2447
+ > ;
2448
+ app. register_asset_loader ( CoolTextLoader )
2449
+ . register_asset_processor ( CoolTextProcessor :: new (
2450
+ RootAssetTransformer :: new ( AddText ) ,
2451
+ CoolTextSaver ,
2452
+ ) )
2453
+ . set_default_asset_processor :: < CoolTextProcessor > ( "cool.ron" ) ;
2454
+
2455
+ let path = Path :: new ( "abc.cool.ron" ) ;
2456
+ source_dir. insert_asset_text (
2457
+ path,
2458
+ r#"(
2459
+ text: "abc",
2460
+ dependencies: [],
2461
+ embedded_dependencies: [],
2462
+ sub_texts: [],
2463
+ )"# ,
2464
+ ) ;
2465
+
2466
+ // Start the app, which also starts the asset processor.
2467
+ app. update ( ) ;
2468
+
2469
+ // Wait for all processing to finish.
2470
+ bevy_tasks:: block_on (
2471
+ app. world ( )
2472
+ . resource :: < AssetProcessor > ( )
2473
+ . data ( )
2474
+ . wait_until_finished ( ) ,
2475
+ ) ;
2476
+
2477
+ let processed_asset = processed_dir. get_asset ( path) . unwrap ( ) ;
2478
+ let processed_asset = str:: from_utf8 ( processed_asset. value ( ) ) . unwrap ( ) ;
2479
+ assert_eq ! (
2480
+ processed_asset,
2481
+ r#"(text:"abc_def",dependencies:[],embedded_dependencies:[],sub_texts:[])"#
2482
+ ) ;
2483
+ }
2484
+
2485
+ // The asset processor currently requires multi_threaded.
2486
+ #[ cfg( feature = "multi_threaded" ) ]
2487
+ #[ test]
2488
+ fn asset_processor_transforms_asset_with_meta ( ) {
2489
+ let AppWithProcessor {
2490
+ mut app,
2491
+ source_dir,
2492
+ processed_dir,
2493
+ } = create_app_with_asset_processor ( ) ;
2494
+
2495
+ struct AddText ;
2496
+
2497
+ impl MutateAsset < CoolText > for AddText {
2498
+ fn mutate ( & self , text : & mut CoolText ) {
2499
+ text. text . push_str ( "_def" ) ;
2500
+ }
2501
+ }
2502
+
2503
+ type CoolTextProcessor = LoadTransformAndSave <
2504
+ CoolTextLoader ,
2505
+ RootAssetTransformer < AddText , CoolText > ,
2506
+ CoolTextSaver ,
2507
+ > ;
2508
+ app. register_asset_loader ( CoolTextLoader )
2509
+ . register_asset_processor ( CoolTextProcessor :: new (
2510
+ RootAssetTransformer :: new ( AddText ) ,
2511
+ CoolTextSaver ,
2512
+ ) ) ;
2513
+
2514
+ let path = Path :: new ( "abc.cool.ron" ) ;
2515
+ source_dir. insert_asset_text (
2516
+ path,
2517
+ r#"(
2518
+ text: "abc",
2519
+ dependencies: [],
2520
+ embedded_dependencies: [],
2521
+ sub_texts: [],
2522
+ )"# ,
2523
+ ) ;
2524
+ source_dir. insert_meta_text ( path, r#"(
2525
+ meta_format_version: "1.0",
2526
+ asset: Process(
2527
+ processor: "bevy_asset::processor::process::LoadTransformAndSave<bevy_asset::tests::CoolTextLoader, bevy_asset::tests::RootAssetTransformer<bevy_asset::tests::asset_processor_transforms_asset_with_meta::AddText, bevy_asset::tests::CoolText>, bevy_asset::tests::CoolTextSaver>",
2528
+ settings: (
2529
+ loader_settings: (),
2530
+ transformer_settings: (),
2531
+ saver_settings: (),
2532
+ ),
2533
+ ),
2534
+ )"# ) ;
2535
+
2536
+ // Start the app, which also starts the asset processor.
2537
+ app. update ( ) ;
2538
+
2539
+ // Wait for all processing to finish.
2540
+ bevy_tasks:: block_on (
2541
+ app. world ( )
2542
+ . resource :: < AssetProcessor > ( )
2543
+ . data ( )
2544
+ . wait_until_finished ( ) ,
2545
+ ) ;
2546
+
2547
+ let processed_asset = processed_dir. get_asset ( path) . unwrap ( ) ;
2548
+ let processed_asset = str:: from_utf8 ( processed_asset. value ( ) ) . unwrap ( ) ;
2549
+ assert_eq ! (
2550
+ processed_asset,
2551
+ r#"(text:"abc_def",dependencies:[],embedded_dependencies:[],sub_texts:[])"#
2552
+ ) ;
2553
+ }
2241
2554
}
0 commit comments