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 } ;
33use bevy_platform:: {
44 collections:: HashMap ,
55 sync:: { PoisonError , RwLock } ,
66} ;
77use core:: { pin:: Pin , task:: Poll } ;
8- use futures_io:: AsyncRead ;
8+ use futures_io:: { AsyncRead , AsyncWrite } ;
99use futures_lite:: { ready, Stream } ;
10- use std:: path:: { Path , PathBuf } ;
10+ use std:: {
11+ io:: { Error , ErrorKind } ,
12+ path:: { Path , PathBuf } ,
13+ } ;
1114
1215use super :: AsyncSeekForward ;
1316
@@ -59,7 +62,9 @@ impl Dir {
5962 ) ;
6063 }
6164
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.
6368 pub fn remove_asset ( & self , path : & Path ) -> Option < Data > {
6469 let mut dir = self . clone ( ) ;
6570 if let Some ( parent) = path. parent ( ) {
@@ -91,6 +96,22 @@ impl Dir {
9196 ) ;
9297 }
9398
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+
94115 pub fn get_or_insert_dir ( & self , path : & Path ) -> Dir {
95116 let mut dir = self . clone ( ) ;
96117 let mut full_path = PathBuf :: new ( ) ;
@@ -108,6 +129,22 @@ impl Dir {
108129 dir
109130 }
110131
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+
111148 pub fn get_dir ( & self , path : & Path ) -> Option < Dir > {
112149 let mut dir = self . clone ( ) ;
113150 for p in path. components ( ) {
@@ -215,6 +252,14 @@ pub struct MemoryAssetReader {
215252 pub root : Dir ,
216253}
217254
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+
218263/// Asset data stored in a [`Dir`].
219264#[ derive( Clone , Debug ) ]
220265pub struct Data {
@@ -230,10 +275,13 @@ pub enum Value {
230275}
231276
232277impl Data {
233- fn path ( & self ) -> & Path {
278+ /// The path that this data was written to.
279+ pub fn path ( & self ) -> & Path {
234280 & self . path
235281 }
236- fn value ( & self ) -> & [ u8 ] {
282+
283+ /// The value in bytes that was written here.
284+ pub fn value ( & self ) -> & [ u8 ] {
237285 match & self . value {
238286 Value :: Vec ( vec) => vec,
239287 Value :: Static ( value) => value,
@@ -296,8 +344,8 @@ impl AsyncSeekForward for DataReader {
296344 self . bytes_read = new_pos as _ ;
297345 Poll :: Ready ( Ok ( new_pos as _ ) )
298346 } else {
299- Poll :: Ready ( Err ( std :: io :: Error :: new (
300- std :: io :: ErrorKind :: InvalidInput ,
347+ Poll :: Ready ( Err ( Error :: new (
348+ ErrorKind :: InvalidInput ,
301349 "seek position is out of range" ,
302350 ) ) )
303351 }
@@ -361,6 +409,183 @@ impl AssetReader for MemoryAssetReader {
361409 }
362410}
363411
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+
364589#[ cfg( test) ]
365590pub mod test {
366591 use super :: Dir ;
0 commit comments