@@ -4,14 +4,55 @@ use std::{
4
4
} ;
5
5
6
6
use bevy_ecs:: { system:: Resource , world:: World } ;
7
+ use bevy_utils:: Duration ;
7
8
8
- use crate :: io:: AssetSourceBuilder ;
9
+ use crate :: io:: {
10
+ AssetReader , AssetSource , AssetSourceBuilder , AssetSourceEvent , AssetWatcher , AssetWriter ,
11
+ ErasedAssetReader , ErasedAssetWriter ,
12
+ } ;
13
+
14
+ /// A [resource](`Resource`) providing access to the temporary directory used by the `temp://`
15
+ /// [asset source](`AssetSource`).
16
+ #[ derive( Resource ) ]
17
+ pub struct TempDirectory {
18
+ directory : TempDirectoryKind ,
19
+ }
20
+
21
+ impl TempDirectory {
22
+ /// Try to create a new [`TempDirectory`] resource, which uses a randomly created
23
+ /// directory in the user's temporary directory. This can fail if the platform does not
24
+ /// provide an appropriate temporary directory, or the directory itself could not be created.
25
+ pub fn new_transient ( ) -> std:: io:: Result < Self > {
26
+ let directory = TempDirectoryKind :: new_transient ( ) ?;
27
+
28
+ Ok ( Self { directory } )
29
+ }
30
+
31
+ /// Create a new [`TempDirectory`] resource, which uses a provided directory to store temporary
32
+ /// assets. It is assumed this directory already exists, and it will _not_ be deleted on exit.
33
+ pub fn new_persistent ( path : impl Into < PathBuf > ) -> Self {
34
+ let directory = TempDirectoryKind :: new_persistent ( path) ;
35
+
36
+ Self { directory }
37
+ }
38
+
39
+ /// Get the [`Path`] to the directory used for temporary assets.
40
+ pub fn path ( & self ) -> & Path {
41
+ self . directory . path ( )
42
+ }
43
+
44
+ /// Persist the current temporary asset directory after application exit.
45
+ pub fn persist ( & mut self ) -> & mut Self {
46
+ self . directory . persist ( ) ;
47
+
48
+ self
49
+ }
50
+ }
9
51
10
52
/// Private resource to store the temporary directory used by `temp://`.
11
53
/// Kept private as it should only be removed on application exit.
12
- #[ derive( Resource ) ]
13
- enum TempDirectory {
14
- /// Uses [`TempDir`](tempfile::TempDir)'s drop behaviour to delete the directory.
54
+ enum TempDirectoryKind {
55
+ /// Uses [`TempDir`](tempfile::TempDir)'s drop behavior to delete the directory.
15
56
/// Note that this is not _guaranteed_ to succeed, so it is possible to leak files from this
16
57
/// option until the underlying OS cleans temporary directories. For secure files, consider using
17
58
/// [`tempfile`](tempfile::tempfile) directly.
@@ -21,13 +62,37 @@ enum TempDirectory {
21
62
Persist ( PathBuf ) ,
22
63
}
23
64
24
- impl TempDirectory {
65
+ impl TempDirectoryKind {
66
+ fn new_transient ( ) -> std:: io:: Result < Self > {
67
+ let directory = tempfile:: TempDir :: with_prefix ( "bevy_" ) ?;
68
+ Ok ( Self :: Delete ( directory) )
69
+ }
70
+
71
+ fn new_persistent ( path : impl Into < PathBuf > ) -> Self {
72
+ Self :: Persist ( path. into ( ) )
73
+ }
74
+
25
75
fn path ( & self ) -> & Path {
26
76
match self {
27
- TempDirectory :: Delete ( x) => x. path ( ) ,
28
- TempDirectory :: Persist ( x) => x. as_ref ( ) ,
77
+ Self :: Delete ( x) => x. as_ref ( ) ,
78
+ Self :: Persist ( x) => x. as_ref ( ) ,
29
79
}
30
80
}
81
+
82
+ fn persist ( & mut self ) -> & mut Self {
83
+ let mut swap = Self :: Persist ( PathBuf :: new ( ) ) ;
84
+
85
+ std:: mem:: swap ( self , & mut swap) ;
86
+
87
+ let new = match swap {
88
+ Self :: Delete ( x) => Self :: Persist ( x. into_path ( ) ) ,
89
+ x @ Self :: Persist ( _) => x,
90
+ } ;
91
+
92
+ * self = new;
93
+
94
+ self
95
+ }
31
96
}
32
97
33
98
pub ( crate ) fn get_temp_source (
@@ -37,20 +102,177 @@ pub(crate) fn get_temp_source(
37
102
let temp_dir = match world. remove_resource :: < TempDirectory > ( ) {
38
103
Some ( resource) => resource,
39
104
None => match temporary_file_path {
40
- Some ( path) => TempDirectory :: Persist ( path. into ( ) ) ,
41
- None => TempDirectory :: Delete ( tempfile :: TempDir :: with_prefix ( "bevy" ) ? ) ,
105
+ Some ( path) => TempDirectory :: new_persistent ( path) ,
106
+ None => TempDirectory :: new_transient ( ) ? ,
42
107
} ,
43
108
} ;
44
109
45
- let path = temp_dir
110
+ let path: & str = temp_dir
46
111
. path ( )
47
112
. as_os_str ( )
48
113
. try_into ( )
49
114
. map_err ( |error| Error :: new ( ErrorKind :: InvalidData , error) ) ?;
50
115
51
- let source = AssetSourceBuilder :: platform_default ( path, None ) ;
116
+ let path = path. to_owned ( ) ;
117
+ let debounce = Duration :: from_millis ( 300 ) ;
118
+
119
+ let source = AssetSourceBuilder :: default ( )
120
+ . with_reader ( TempAssetReader :: get_default ( path. clone ( ) ) )
121
+ . with_writer ( TempAssetWriter :: get_default ( path. clone ( ) ) )
122
+ . with_watcher ( TempAssetWatcher :: get_default ( path. clone ( ) , debounce) )
123
+ . with_watch_warning ( TempAssetWatcher :: get_default_watch_warning ( ) ) ;
52
124
53
125
world. insert_resource ( temp_dir) ;
54
126
55
127
Ok ( source)
56
128
}
129
+
130
+ struct TempAssetReader {
131
+ inner : Box < dyn ErasedAssetReader > ,
132
+ }
133
+
134
+ impl TempAssetReader {
135
+ fn get_default ( path : String ) -> impl FnMut ( ) -> Box < dyn ErasedAssetReader > + Send + Sync {
136
+ move || {
137
+ let mut getter = AssetSource :: get_default_reader ( path. clone ( ) ) ;
138
+ let inner = getter ( ) ;
139
+
140
+ Box :: new ( Self { inner } )
141
+ }
142
+ }
143
+ }
144
+
145
+ impl AssetReader for TempAssetReader {
146
+ async fn read < ' a > (
147
+ & ' a self ,
148
+ path : & ' a Path ,
149
+ ) -> Result < Box < crate :: io:: Reader < ' a > > , crate :: io:: AssetReaderError > {
150
+ self . inner . read ( path) . await
151
+ }
152
+
153
+ async fn read_meta < ' a > (
154
+ & ' a self ,
155
+ path : & ' a Path ,
156
+ ) -> Result < Box < crate :: io:: Reader < ' a > > , crate :: io:: AssetReaderError > {
157
+ self . inner . read_meta ( path) . await
158
+ }
159
+
160
+ async fn read_directory < ' a > (
161
+ & ' a self ,
162
+ path : & ' a Path ,
163
+ ) -> Result < Box < crate :: io:: PathStream > , crate :: io:: AssetReaderError > {
164
+ self . inner . read_directory ( path) . await
165
+ }
166
+
167
+ async fn is_directory < ' a > (
168
+ & ' a self ,
169
+ path : & ' a Path ,
170
+ ) -> Result < bool , crate :: io:: AssetReaderError > {
171
+ self . inner . is_directory ( path) . await
172
+ }
173
+ }
174
+
175
+ struct TempAssetWriter {
176
+ inner : Box < dyn ErasedAssetWriter > ,
177
+ }
178
+
179
+ impl TempAssetWriter {
180
+ fn get_default (
181
+ path : String ,
182
+ ) -> impl FnMut ( bool ) -> Option < Box < dyn ErasedAssetWriter > > + Send + Sync {
183
+ move |condition| {
184
+ let mut getter = AssetSource :: get_default_writer ( path. clone ( ) ) ;
185
+ let inner = getter ( condition) ?;
186
+
187
+ Some ( Box :: new ( Self { inner } ) )
188
+ }
189
+ }
190
+ }
191
+
192
+ impl AssetWriter for TempAssetWriter {
193
+ async fn write < ' a > (
194
+ & ' a self ,
195
+ path : & ' a Path ,
196
+ ) -> Result < Box < crate :: io:: Writer > , crate :: io:: AssetWriterError > {
197
+ self . inner . write ( path) . await
198
+ }
199
+
200
+ async fn write_meta < ' a > (
201
+ & ' a self ,
202
+ path : & ' a Path ,
203
+ ) -> Result < Box < crate :: io:: Writer > , crate :: io:: AssetWriterError > {
204
+ self . inner . write_meta ( path) . await
205
+ }
206
+
207
+ async fn remove < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , crate :: io:: AssetWriterError > {
208
+ self . inner . remove ( path) . await
209
+ }
210
+
211
+ async fn remove_meta < ' a > ( & ' a self , path : & ' a Path ) -> Result < ( ) , crate :: io:: AssetWriterError > {
212
+ self . inner . remove_meta ( path) . await
213
+ }
214
+
215
+ async fn rename < ' a > (
216
+ & ' a self ,
217
+ old_path : & ' a Path ,
218
+ new_path : & ' a Path ,
219
+ ) -> Result < ( ) , crate :: io:: AssetWriterError > {
220
+ self . inner . rename ( old_path, new_path) . await
221
+ }
222
+
223
+ async fn rename_meta < ' a > (
224
+ & ' a self ,
225
+ old_path : & ' a Path ,
226
+ new_path : & ' a Path ,
227
+ ) -> Result < ( ) , crate :: io:: AssetWriterError > {
228
+ self . inner . rename_meta ( old_path, new_path) . await
229
+ }
230
+
231
+ async fn remove_directory < ' a > (
232
+ & ' a self ,
233
+ path : & ' a Path ,
234
+ ) -> Result < ( ) , crate :: io:: AssetWriterError > {
235
+ self . inner . remove_directory ( path) . await
236
+ }
237
+
238
+ async fn remove_empty_directory < ' a > (
239
+ & ' a self ,
240
+ path : & ' a Path ,
241
+ ) -> Result < ( ) , crate :: io:: AssetWriterError > {
242
+ self . inner . remove_empty_directory ( path) . await
243
+ }
244
+
245
+ async fn remove_assets_in_directory < ' a > (
246
+ & ' a self ,
247
+ path : & ' a Path ,
248
+ ) -> Result < ( ) , crate :: io:: AssetWriterError > {
249
+ self . inner . remove_assets_in_directory ( path) . await
250
+ }
251
+ }
252
+
253
+ struct TempAssetWatcher {
254
+ _inner : Box < dyn AssetWatcher > ,
255
+ }
256
+
257
+ impl TempAssetWatcher {
258
+ fn get_default (
259
+ path : String ,
260
+ file_debounce_wait_time : Duration ,
261
+ ) -> impl FnMut ( crossbeam_channel:: Sender < AssetSourceEvent > ) -> Option < Box < dyn AssetWatcher > >
262
+ + Send
263
+ + Sync {
264
+ move |channel| {
265
+ let mut getter =
266
+ AssetSource :: get_default_watcher ( path. clone ( ) , file_debounce_wait_time) ;
267
+ let _inner = getter ( channel) ?;
268
+
269
+ Some ( Box :: new ( Self { _inner } ) )
270
+ }
271
+ }
272
+
273
+ fn get_default_watch_warning ( ) -> & ' static str {
274
+ AssetSource :: get_default_watch_warning ( )
275
+ }
276
+ }
277
+
278
+ impl AssetWatcher for TempAssetWatcher { }
0 commit comments