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 {
@@ -230,10 +275,13 @@ pub enum Value {
230
275
}
231
276
232
277
impl Data {
233
- fn path ( & self ) -> & Path {
278
+ /// The path that this data was written to.
279
+ pub fn path ( & self ) -> & Path {
234
280
& self . path
235
281
}
236
- fn value ( & self ) -> & [ u8 ] {
282
+
283
+ /// The value in bytes that was written here.
284
+ pub fn value ( & self ) -> & [ u8 ] {
237
285
match & self . value {
238
286
Value :: Vec ( vec) => vec,
239
287
Value :: Static ( value) => value,
@@ -296,8 +344,8 @@ impl AsyncSeekForward for DataReader {
296
344
self . bytes_read = new_pos as _ ;
297
345
Poll :: Ready ( Ok ( new_pos as _ ) )
298
346
} else {
299
- Poll :: Ready ( Err ( std :: io :: Error :: new (
300
- std :: io :: ErrorKind :: InvalidInput ,
347
+ Poll :: Ready ( Err ( Error :: new (
348
+ ErrorKind :: InvalidInput ,
301
349
"seek position is out of range" ,
302
350
) ) )
303
351
}
@@ -361,6 +409,183 @@ impl AssetReader for MemoryAssetReader {
361
409
}
362
410
}
363
411
412
+ /// A writer that writes into [`Dir`], buffering internally until flushed/closed.
413
+ struct DataWriter {
414
+ /// The dir to write to.
415
+ dir : Dir ,
416
+ /// The path to write to.
417
+ path : PathBuf ,
418
+ /// The current buffer of data.
419
+ ///
420
+ /// This will include data that has been flushed already.
421
+ current_data : Vec < u8 > ,
422
+ /// Whether to write to the data or to the meta.
423
+ is_meta_writer : bool ,
424
+ }
425
+
426
+ impl AsyncWrite for DataWriter {
427
+ fn poll_write (
428
+ self : Pin < & mut Self > ,
429
+ _: & mut core:: task:: Context < ' _ > ,
430
+ buf : & [ u8 ] ,
431
+ ) -> Poll < std:: io:: Result < usize > > {
432
+ self . get_mut ( ) . current_data . extend_from_slice ( buf) ;
433
+ Poll :: Ready ( Ok ( buf. len ( ) ) )
434
+ }
435
+
436
+ fn poll_flush (
437
+ self : Pin < & mut Self > ,
438
+ _: & mut core:: task:: Context < ' _ > ,
439
+ ) -> Poll < std:: io:: Result < ( ) > > {
440
+ // Write the data to our fake disk. This means we will repeatedly reinsert the asset.
441
+ if self . is_meta_writer {
442
+ self . dir . insert_meta ( & self . path , self . current_data . clone ( ) ) ;
443
+ } else {
444
+ self . dir . insert_asset ( & self . path , self . current_data . clone ( ) ) ;
445
+ }
446
+ Poll :: Ready ( Ok ( ( ) ) )
447
+ }
448
+
449
+ fn poll_close (
450
+ self : Pin < & mut Self > ,
451
+ cx : & mut core:: task:: Context < ' _ > ,
452
+ ) -> Poll < std:: io:: Result < ( ) > > {
453
+ // A flush will just write the data to Dir, which is all we need to do for close.
454
+ self . poll_flush ( cx)
455
+ }
456
+ }
457
+
458
+ impl AssetWriter for MemoryAssetWriter {
459
+ async fn write < ' a > ( & ' a self , path : & ' a Path ) -> Result < Box < super :: Writer > , AssetWriterError > {
460
+ Ok ( Box :: new ( DataWriter {
461
+ dir : self . root . clone ( ) ,
462
+ path : path. to_owned ( ) ,
463
+ current_data : vec ! [ ] ,
464
+ is_meta_writer : false ,
465
+ } ) )
466
+ }
467
+
468
+ async fn write_meta < ' a > (
469
+ & ' a self ,
470
+ path : & ' a Path ,
471
+ ) -> Result < Box < super :: Writer > , AssetWriterError > {
472
+ Ok ( Box :: new ( DataWriter {
473
+ dir : self . root . clone ( ) ,
474
+ path : path. to_owned ( ) ,
475
+ current_data : vec ! [ ] ,
476
+ is_meta_writer : true ,
477
+ } ) )
478
+ }
479
+
480
+ async fn remove < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , AssetWriterError > {
481
+ if self . root . remove_asset ( path) . is_none ( ) {
482
+ return Err ( AssetWriterError :: Io ( Error :: new (
483
+ ErrorKind :: NotFound ,
484
+ "no such file" ,
485
+ ) ) ) ;
486
+ }
487
+ Ok ( ( ) )
488
+ }
489
+
490
+ async fn remove_meta < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , AssetWriterError > {
491
+ self . root . remove_metadata ( path) ;
492
+ Ok ( ( ) )
493
+ }
494
+
495
+ async fn rename < ' a > (
496
+ & ' a self ,
497
+ old_path : & ' a Path ,
498
+ new_path : & ' a Path ,
499
+ ) -> Result < ( ) , AssetWriterError > {
500
+ let Some ( old_asset) = self . root . get_asset ( old_path) else {
501
+ return Err ( AssetWriterError :: Io ( Error :: new (
502
+ ErrorKind :: NotFound ,
503
+ "no such file" ,
504
+ ) ) ) ;
505
+ } ;
506
+ self . root . insert_asset ( new_path, old_asset. value ) ;
507
+ // Remove the asset after instead of before since otherwise there'd be a moment where the
508
+ // Dir is unlocked and missing both the old and new paths. This just prevents race
509
+ // conditions.
510
+ self . root . remove_asset ( old_path) ;
511
+ Ok ( ( ) )
512
+ }
513
+
514
+ async fn rename_meta < ' a > (
515
+ & ' a self ,
516
+ old_path : & ' a Path ,
517
+ new_path : & ' a Path ,
518
+ ) -> Result < ( ) , AssetWriterError > {
519
+ let Some ( old_meta) = self . root . get_metadata ( old_path) else {
520
+ return Err ( AssetWriterError :: Io ( Error :: new (
521
+ ErrorKind :: NotFound ,
522
+ "no such file" ,
523
+ ) ) ) ;
524
+ } ;
525
+ self . root . insert_meta ( new_path, old_meta. value ) ;
526
+ // Remove the meta after instead of before since otherwise there'd be a moment where the
527
+ // Dir is unlocked and missing both the old and new paths. This just prevents race
528
+ // conditions.
529
+ self . root . remove_metadata ( old_path) ;
530
+ Ok ( ( ) )
531
+ }
532
+
533
+ async fn create_directory < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , AssetWriterError > {
534
+ // Just pretend we're on a file system that doesn't consider directory re-creation a
535
+ // failure.
536
+ self . root . get_or_insert_dir ( path) ;
537
+ Ok ( ( ) )
538
+ }
539
+
540
+ async fn remove_directory < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , AssetWriterError > {
541
+ if self . root . remove_dir ( path) . is_none ( ) {
542
+ return Err ( AssetWriterError :: Io ( Error :: new (
543
+ ErrorKind :: NotFound ,
544
+ "no such dir" ,
545
+ ) ) ) ;
546
+ }
547
+ Ok ( ( ) )
548
+ }
549
+
550
+ async fn remove_empty_directory < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , AssetWriterError > {
551
+ let Some ( dir) = self . root . get_dir ( path) else {
552
+ return Err ( AssetWriterError :: Io ( Error :: new (
553
+ ErrorKind :: NotFound ,
554
+ "no such dir" ,
555
+ ) ) ) ;
556
+ } ;
557
+
558
+ let dir = dir. 0 . read ( ) . unwrap ( ) ;
559
+ if !dir. assets . is_empty ( ) || !dir. metadata . is_empty ( ) || !dir. dirs . is_empty ( ) {
560
+ return Err ( AssetWriterError :: Io ( Error :: new (
561
+ ErrorKind :: DirectoryNotEmpty ,
562
+ "not empty" ,
563
+ ) ) ) ;
564
+ }
565
+
566
+ self . root . remove_dir ( path) ;
567
+ Ok ( ( ) )
568
+ }
569
+
570
+ async fn remove_assets_in_directory < ' a > (
571
+ & ' a self ,
572
+ path : & ' a Path ,
573
+ ) -> Result < ( ) , AssetWriterError > {
574
+ let Some ( dir) = self . root . get_dir ( path) else {
575
+ return Err ( AssetWriterError :: Io ( Error :: new (
576
+ ErrorKind :: NotFound ,
577
+ "no such dir" ,
578
+ ) ) ) ;
579
+ } ;
580
+
581
+ let mut dir = dir. 0 . write ( ) . unwrap ( ) ;
582
+ dir. assets . clear ( ) ;
583
+ dir. dirs . clear ( ) ;
584
+ dir. metadata . clear ( ) ;
585
+ Ok ( ( ) )
586
+ }
587
+ }
588
+
364
589
#[ cfg( test) ]
365
590
pub mod test {
366
591
use super :: Dir ;
0 commit comments