@@ -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,315 @@ 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
+ #[ expect( clippy:: allow_attributes, reason = "this is only sometimes unused" ) ]
2356
+ #[ allow(
2357
+ unused,
2358
+ reason = "We only use this for asset processor tests, which are only compiled with the `multi_threaded` feature."
2359
+ ) ]
2360
+ fn new ( m : M ) -> Self {
2361
+ Self ( m, PhantomData )
2362
+ }
2363
+ }
2364
+
2365
+ impl < M : MutateAsset < A > , A : Asset > AssetTransformer for RootAssetTransformer < M , A > {
2366
+ type AssetInput = A ;
2367
+ type AssetOutput = A ;
2368
+ type Error = std:: io:: Error ;
2369
+ type Settings = ( ) ;
2370
+
2371
+ async fn transform < ' a > (
2372
+ & ' a self ,
2373
+ mut asset : TransformedAsset < A > ,
2374
+ _settings : & ' a Self :: Settings ,
2375
+ ) -> Result < TransformedAsset < A > , Self :: Error > {
2376
+ self . 0 . mutate ( asset. get_mut ( ) ) ;
2377
+ Ok ( asset)
2378
+ }
2379
+ }
2380
+
2381
+ #[ cfg( feature = "multi_threaded" ) ]
2382
+ use crate :: processor:: { AssetProcessor , LoadTransformAndSave } ;
2383
+
2384
+ // The asset processor currently requires multi_threaded.
2385
+ #[ cfg( feature = "multi_threaded" ) ]
2386
+ #[ test]
2387
+ fn no_meta_or_default_processor_copies_asset ( ) {
2388
+ // Assets without a meta file or a default processor should still be accessible in the
2389
+ // processed path. Note: This isn't exactly the desired property - we don't want the assets
2390
+ // to be copied to the processed directory. We just want these assets to still be loadable
2391
+ // if we no longer have the source directory. This could be done with a symlink instead of a
2392
+ // copy.
2393
+
2394
+ let AppWithProcessor {
2395
+ mut app,
2396
+ source_dir,
2397
+ processed_dir,
2398
+ } = create_app_with_asset_processor ( ) ;
2399
+
2400
+ let path = Path :: new ( "abc.cool.ron" ) ;
2401
+ let source_asset = r#"(
2402
+ text: "abc",
2403
+ dependencies: [],
2404
+ embedded_dependencies: [],
2405
+ sub_texts: [],
2406
+ )"# ;
2407
+
2408
+ source_dir. insert_asset_text ( path, source_asset) ;
2409
+
2410
+ // Start the app, which also starts the asset processor.
2411
+ app. update ( ) ;
2412
+
2413
+ // Wait for all processing to finish.
2414
+ bevy_tasks:: block_on (
2415
+ app. world ( )
2416
+ . resource :: < AssetProcessor > ( )
2417
+ . data ( )
2418
+ . wait_until_finished ( ) ,
2419
+ ) ;
2420
+
2421
+ let processed_asset = processed_dir. get_asset ( path) . unwrap ( ) ;
2422
+ let processed_asset = str:: from_utf8 ( processed_asset. value ( ) ) . unwrap ( ) ;
2423
+ assert_eq ! ( processed_asset, source_asset) ;
2424
+ }
2425
+
2426
+ // The asset processor currently requires multi_threaded.
2427
+ #[ cfg( feature = "multi_threaded" ) ]
2428
+ #[ test]
2429
+ fn asset_processor_transforms_asset_default_processor ( ) {
2430
+ let AppWithProcessor {
2431
+ mut app,
2432
+ source_dir,
2433
+ processed_dir,
2434
+ } = create_app_with_asset_processor ( ) ;
2435
+
2436
+ struct AddText ;
2437
+
2438
+ impl MutateAsset < CoolText > for AddText {
2439
+ fn mutate ( & self , text : & mut CoolText ) {
2440
+ text. text . push_str ( "_def" ) ;
2441
+ }
2442
+ }
2443
+
2444
+ type CoolTextProcessor = LoadTransformAndSave <
2445
+ CoolTextLoader ,
2446
+ RootAssetTransformer < AddText , CoolText > ,
2447
+ CoolTextSaver ,
2448
+ > ;
2449
+ app. register_asset_loader ( CoolTextLoader )
2450
+ . register_asset_processor ( CoolTextProcessor :: new (
2451
+ RootAssetTransformer :: new ( AddText ) ,
2452
+ CoolTextSaver ,
2453
+ ) )
2454
+ . set_default_asset_processor :: < CoolTextProcessor > ( "cool.ron" ) ;
2455
+
2456
+ let path = Path :: new ( "abc.cool.ron" ) ;
2457
+ source_dir. insert_asset_text (
2458
+ path,
2459
+ r#"(
2460
+ text: "abc",
2461
+ dependencies: [],
2462
+ embedded_dependencies: [],
2463
+ sub_texts: [],
2464
+ )"# ,
2465
+ ) ;
2466
+
2467
+ // Start the app, which also starts the asset processor.
2468
+ app. update ( ) ;
2469
+
2470
+ // Wait for all processing to finish.
2471
+ bevy_tasks:: block_on (
2472
+ app. world ( )
2473
+ . resource :: < AssetProcessor > ( )
2474
+ . data ( )
2475
+ . wait_until_finished ( ) ,
2476
+ ) ;
2477
+
2478
+ let processed_asset = processed_dir. get_asset ( path) . unwrap ( ) ;
2479
+ let processed_asset = str:: from_utf8 ( processed_asset. value ( ) ) . unwrap ( ) ;
2480
+ assert_eq ! (
2481
+ processed_asset,
2482
+ r#"(text:"abc_def",dependencies:[],embedded_dependencies:[],sub_texts:[])"#
2483
+ ) ;
2484
+ }
2485
+
2486
+ // The asset processor currently requires multi_threaded.
2487
+ #[ cfg( feature = "multi_threaded" ) ]
2488
+ #[ test]
2489
+ fn asset_processor_transforms_asset_with_meta ( ) {
2490
+ let AppWithProcessor {
2491
+ mut app,
2492
+ source_dir,
2493
+ processed_dir,
2494
+ } = create_app_with_asset_processor ( ) ;
2495
+
2496
+ struct AddText ;
2497
+
2498
+ impl MutateAsset < CoolText > for AddText {
2499
+ fn mutate ( & self , text : & mut CoolText ) {
2500
+ text. text . push_str ( "_def" ) ;
2501
+ }
2502
+ }
2503
+
2504
+ type CoolTextProcessor = LoadTransformAndSave <
2505
+ CoolTextLoader ,
2506
+ RootAssetTransformer < AddText , CoolText > ,
2507
+ CoolTextSaver ,
2508
+ > ;
2509
+ app. register_asset_loader ( CoolTextLoader )
2510
+ . register_asset_processor ( CoolTextProcessor :: new (
2511
+ RootAssetTransformer :: new ( AddText ) ,
2512
+ CoolTextSaver ,
2513
+ ) ) ;
2514
+
2515
+ let path = Path :: new ( "abc.cool.ron" ) ;
2516
+ source_dir. insert_asset_text (
2517
+ path,
2518
+ r#"(
2519
+ text: "abc",
2520
+ dependencies: [],
2521
+ embedded_dependencies: [],
2522
+ sub_texts: [],
2523
+ )"# ,
2524
+ ) ;
2525
+ source_dir. insert_meta_text ( path, r#"(
2526
+ meta_format_version: "1.0",
2527
+ asset: Process(
2528
+ 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>",
2529
+ settings: (
2530
+ loader_settings: (),
2531
+ transformer_settings: (),
2532
+ saver_settings: (),
2533
+ ),
2534
+ ),
2535
+ )"# ) ;
2536
+
2537
+ // Start the app, which also starts the asset processor.
2538
+ app. update ( ) ;
2539
+
2540
+ // Wait for all processing to finish.
2541
+ bevy_tasks:: block_on (
2542
+ app. world ( )
2543
+ . resource :: < AssetProcessor > ( )
2544
+ . data ( )
2545
+ . wait_until_finished ( ) ,
2546
+ ) ;
2547
+
2548
+ let processed_asset = processed_dir. get_asset ( path) . unwrap ( ) ;
2549
+ let processed_asset = str:: from_utf8 ( processed_asset. value ( ) ) . unwrap ( ) ;
2550
+ assert_eq ! (
2551
+ processed_asset,
2552
+ r#"(text:"abc_def",dependencies:[],embedded_dependencies:[],sub_texts:[])"#
2553
+ ) ;
2554
+ }
2241
2555
}
0 commit comments