1
- use crate :: io:: { AssetReader , AssetReaderError , PathStream , Reader } ;
2
- use alloc:: { borrow:: ToOwned , boxed:: Box , sync:: Arc , vec:: Vec } ;
1
+ use crate :: io:: { AssetReader , AssetReaderError , AssetWriter , AssetWriterError , PathStream , Reader } ;
2
+ use alloc:: { borrow:: ToOwned , boxed:: Box , sync:: Arc , vec, vec :: Vec } ;
3
3
use bevy_platform:: {
4
4
collections:: HashMap ,
5
5
sync:: { PoisonError , RwLock } ,
6
6
} ;
7
7
use core:: { pin:: Pin , task:: Poll } ;
8
- use futures_io:: AsyncRead ;
8
+ use futures_io:: { AsyncRead , AsyncWrite } ;
9
9
use futures_lite:: { ready, Stream } ;
10
- use std:: path:: { Path , PathBuf } ;
10
+ use std:: {
11
+ io:: { Error , ErrorKind } ,
12
+ path:: { Path , PathBuf } ,
13
+ } ;
11
14
12
15
use super :: AsyncSeekForward ;
13
16
@@ -59,7 +62,9 @@ impl Dir {
59
62
) ;
60
63
}
61
64
62
- /// Removes the stored asset at `path` and returns the `Data` stored if found and otherwise `None`.
65
+ /// Removes the stored asset at `path`.
66
+ ///
67
+ /// Returns the [`Data`] stored if found, [`None`] otherwise.
63
68
pub fn remove_asset ( & self , path : & Path ) -> Option < Data > {
64
69
let mut dir = self . clone ( ) ;
65
70
if let Some ( parent) = path. parent ( ) {
@@ -91,6 +96,22 @@ impl Dir {
91
96
) ;
92
97
}
93
98
99
+ /// Removes the stored metadata at `path`.
100
+ ///
101
+ /// Returns the [`Data`] stored if found, [`None`] otherwise.
102
+ pub fn remove_metadata ( & self , path : & Path ) -> Option < Data > {
103
+ let mut dir = self . clone ( ) ;
104
+ if let Some ( parent) = path. parent ( ) {
105
+ dir = self . get_or_insert_dir ( parent) ;
106
+ }
107
+ let key: Box < str > = path. file_name ( ) . unwrap ( ) . to_string_lossy ( ) . into ( ) ;
108
+ dir. 0
109
+ . write ( )
110
+ . unwrap_or_else ( PoisonError :: into_inner)
111
+ . metadata
112
+ . remove ( & key)
113
+ }
114
+
94
115
pub fn get_or_insert_dir ( & self , path : & Path ) -> Dir {
95
116
let mut dir = self . clone ( ) ;
96
117
let mut full_path = PathBuf :: new ( ) ;
@@ -108,6 +129,22 @@ impl Dir {
108
129
dir
109
130
}
110
131
132
+ /// Removes the dir at `path`.
133
+ ///
134
+ /// Returns the [`Dir`] stored if found, [`None`] otherwise.
135
+ pub fn remove_dir ( & self , path : & Path ) -> Option < Dir > {
136
+ let mut dir = self . clone ( ) ;
137
+ if let Some ( parent) = path. parent ( ) {
138
+ dir = self . get_or_insert_dir ( parent) ;
139
+ }
140
+ let key: Box < str > = path. file_name ( ) . unwrap ( ) . to_string_lossy ( ) . into ( ) ;
141
+ dir. 0
142
+ . write ( )
143
+ . unwrap_or_else ( PoisonError :: into_inner)
144
+ . dirs
145
+ . remove ( & key)
146
+ }
147
+
111
148
pub fn get_dir ( & self , path : & Path ) -> Option < Dir > {
112
149
let mut dir = self . clone ( ) ;
113
150
for p in path. components ( ) {
@@ -215,6 +252,14 @@ pub struct MemoryAssetReader {
215
252
pub root : Dir ,
216
253
}
217
254
255
+ /// In-memory [`AssetWriter`] implementation.
256
+ ///
257
+ /// This is primarily intended for unit tests.
258
+ #[ derive( Default , Clone ) ]
259
+ pub struct MemoryAssetWriter {
260
+ pub root : Dir ,
261
+ }
262
+
218
263
/// Asset data stored in a [`Dir`].
219
264
#[ derive( Clone , Debug ) ]
220
265
pub struct Data {
@@ -296,8 +341,8 @@ impl AsyncSeekForward for DataReader {
296
341
self . bytes_read = new_pos as _ ;
297
342
Poll :: Ready ( Ok ( new_pos as _ ) )
298
343
} else {
299
- Poll :: Ready ( Err ( std :: io :: Error :: new (
300
- std :: io :: ErrorKind :: InvalidInput ,
344
+ Poll :: Ready ( Err ( Error :: new (
345
+ ErrorKind :: InvalidInput ,
301
346
"seek position is out of range" ,
302
347
) ) )
303
348
}
@@ -361,6 +406,183 @@ impl AssetReader for MemoryAssetReader {
361
406
}
362
407
}
363
408
409
+ /// A writer that writes into [`Dir`], buffering internally until flushed/closed.
410
+ struct DataWriter {
411
+ /// The dir to write to.
412
+ dir : Dir ,
413
+ /// The path to write to.
414
+ path : PathBuf ,
415
+ /// The current buffer of data.
416
+ ///
417
+ /// This will include data that has been flushed already.
418
+ current_data : Vec < u8 > ,
419
+ /// Whether to write to the data or to the meta.
420
+ write_meta : bool ,
421
+ }
422
+
423
+ impl AsyncWrite for DataWriter {
424
+ fn poll_write (
425
+ self : Pin < & mut Self > ,
426
+ _: & mut core:: task:: Context < ' _ > ,
427
+ buf : & [ u8 ] ,
428
+ ) -> Poll < std:: io:: Result < usize > > {
429
+ self . get_mut ( ) . current_data . extend_from_slice ( buf) ;
430
+ Poll :: Ready ( Ok ( buf. len ( ) ) )
431
+ }
432
+
433
+ fn poll_flush (
434
+ self : Pin < & mut Self > ,
435
+ _: & mut core:: task:: Context < ' _ > ,
436
+ ) -> Poll < std:: io:: Result < ( ) > > {
437
+ // Write the data to our fake disk. This means we will repeatedly reinsert the asset.
438
+ if self . write_meta {
439
+ self . dir . insert_meta ( & self . path , self . current_data . clone ( ) ) ;
440
+ } else {
441
+ self . dir . insert_asset ( & self . path , self . current_data . clone ( ) ) ;
442
+ }
443
+ Poll :: Ready ( Ok ( ( ) ) )
444
+ }
445
+
446
+ fn poll_close (
447
+ self : Pin < & mut Self > ,
448
+ cx : & mut core:: task:: Context < ' _ > ,
449
+ ) -> Poll < std:: io:: Result < ( ) > > {
450
+ // A flush will just write the data to Dir, which is all we need to do for close.
451
+ self . poll_flush ( cx)
452
+ }
453
+ }
454
+
455
+ impl AssetWriter for MemoryAssetWriter {
456
+ async fn write < ' a > ( & ' a self , path : & ' a Path ) -> Result < Box < super :: Writer > , AssetWriterError > {
457
+ Ok ( Box :: new ( DataWriter {
458
+ dir : self . root . clone ( ) ,
459
+ path : path. to_owned ( ) ,
460
+ current_data : vec ! [ ] ,
461
+ write_meta : false ,
462
+ } ) )
463
+ }
464
+
465
+ async fn write_meta < ' a > (
466
+ & ' a self ,
467
+ path : & ' a Path ,
468
+ ) -> Result < Box < super :: Writer > , AssetWriterError > {
469
+ Ok ( Box :: new ( DataWriter {
470
+ dir : self . root . clone ( ) ,
471
+ path : path. to_owned ( ) ,
472
+ current_data : vec ! [ ] ,
473
+ write_meta : true ,
474
+ } ) )
475
+ }
476
+
477
+ async fn remove < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , AssetWriterError > {
478
+ if self . root . remove_asset ( path) . is_none ( ) {
479
+ return Err ( AssetWriterError :: Io ( Error :: new (
480
+ ErrorKind :: NotFound ,
481
+ "no such file" ,
482
+ ) ) ) ;
483
+ }
484
+ Ok ( ( ) )
485
+ }
486
+
487
+ async fn remove_meta < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , AssetWriterError > {
488
+ self . root . remove_metadata ( path) ;
489
+ Ok ( ( ) )
490
+ }
491
+
492
+ async fn rename < ' a > (
493
+ & ' a self ,
494
+ old_path : & ' a Path ,
495
+ new_path : & ' a Path ,
496
+ ) -> Result < ( ) , AssetWriterError > {
497
+ let Some ( old_asset) = self . root . get_asset ( old_path) else {
498
+ return Err ( AssetWriterError :: Io ( Error :: new (
499
+ ErrorKind :: NotFound ,
500
+ "no such file" ,
501
+ ) ) ) ;
502
+ } ;
503
+ self . root . insert_asset ( new_path, old_asset. value ) ;
504
+ // Remove the asset after instead of before since otherwise there'd be a moment where the
505
+ // Dir is unlocked and missing both the old and new paths. This just prevents race
506
+ // conditions.
507
+ self . root . remove_asset ( old_path) ;
508
+ Ok ( ( ) )
509
+ }
510
+
511
+ async fn rename_meta < ' a > (
512
+ & ' a self ,
513
+ old_path : & ' a Path ,
514
+ new_path : & ' a Path ,
515
+ ) -> Result < ( ) , AssetWriterError > {
516
+ let Some ( old_meta) = self . root . get_metadata ( old_path) else {
517
+ return Err ( AssetWriterError :: Io ( Error :: new (
518
+ ErrorKind :: NotFound ,
519
+ "no such file" ,
520
+ ) ) ) ;
521
+ } ;
522
+ self . root . insert_meta ( new_path, old_meta. value ) ;
523
+ // Remove the meta after instead of before since otherwise there'd be a moment where the
524
+ // Dir is unlocked and missing both the old and new paths. This just prevents race
525
+ // conditions.
526
+ self . root . remove_metadata ( old_path) ;
527
+ Ok ( ( ) )
528
+ }
529
+
530
+ async fn create_directory < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , AssetWriterError > {
531
+ // Just pretend we're on a file system that doesn't consider directory re-creation a
532
+ // failure.
533
+ self . root . get_or_insert_dir ( path) ;
534
+ Ok ( ( ) )
535
+ }
536
+
537
+ async fn remove_directory < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , AssetWriterError > {
538
+ if self . root . remove_dir ( path) . is_none ( ) {
539
+ return Err ( AssetWriterError :: Io ( Error :: new (
540
+ ErrorKind :: NotFound ,
541
+ "no such dir" ,
542
+ ) ) ) ;
543
+ }
544
+ Ok ( ( ) )
545
+ }
546
+
547
+ async fn remove_empty_directory < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , AssetWriterError > {
548
+ let Some ( dir) = self . root . get_dir ( path) else {
549
+ return Err ( AssetWriterError :: Io ( Error :: new (
550
+ ErrorKind :: NotFound ,
551
+ "no such dir" ,
552
+ ) ) ) ;
553
+ } ;
554
+
555
+ let dir = dir. 0 . read ( ) . unwrap ( ) ;
556
+ if !dir. assets . is_empty ( ) || !dir. metadata . is_empty ( ) || !dir. dirs . is_empty ( ) {
557
+ return Err ( AssetWriterError :: Io ( Error :: new (
558
+ ErrorKind :: DirectoryNotEmpty ,
559
+ "not empty" ,
560
+ ) ) ) ;
561
+ }
562
+
563
+ self . root . remove_dir ( path) ;
564
+ Ok ( ( ) )
565
+ }
566
+
567
+ async fn remove_assets_in_directory < ' a > (
568
+ & ' a self ,
569
+ path : & ' a Path ,
570
+ ) -> Result < ( ) , AssetWriterError > {
571
+ let Some ( dir) = self . root . get_dir ( path) else {
572
+ return Err ( AssetWriterError :: Io ( Error :: new (
573
+ ErrorKind :: NotFound ,
574
+ "no such dir" ,
575
+ ) ) ) ;
576
+ } ;
577
+
578
+ let mut dir = dir. 0 . write ( ) . unwrap ( ) ;
579
+ dir. assets . clear ( ) ;
580
+ dir. dirs . clear ( ) ;
581
+ dir. metadata . clear ( ) ;
582
+ Ok ( ( ) )
583
+ }
584
+ }
585
+
364
586
#[ cfg( test) ]
365
587
pub mod test {
366
588
use super :: Dir ;
0 commit comments