Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/src/io/memory.rs
9374 views
1
use crate::io::{
2
AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream, Reader,
3
ReaderNotSeekableError, SeekableReader,
4
};
5
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec, vec::Vec};
6
use bevy_platform::{
7
collections::HashMap,
8
sync::{PoisonError, RwLock},
9
};
10
use core::{pin::Pin, task::Poll};
11
use futures_io::{AsyncRead, AsyncWrite};
12
use futures_lite::Stream;
13
use std::{
14
io::{Error, ErrorKind, SeekFrom},
15
path::{Path, PathBuf},
16
};
17
18
use super::AsyncSeek;
19
20
#[derive(Default, Debug)]
21
struct DirInternal {
22
assets: HashMap<Box<str>, Data>,
23
metadata: HashMap<Box<str>, Data>,
24
dirs: HashMap<Box<str>, Dir>,
25
path: PathBuf,
26
}
27
28
/// A clone-able (internally Arc-ed) / thread-safe "in memory" filesystem.
29
/// This is built for [`MemoryAssetReader`] and is primarily intended for unit tests.
30
#[derive(Default, Clone, Debug)]
31
pub struct Dir(Arc<RwLock<DirInternal>>);
32
33
impl Dir {
34
/// Creates a new [`Dir`] for the given `path`.
35
pub fn new(path: PathBuf) -> Self {
36
Self(Arc::new(RwLock::new(DirInternal {
37
path,
38
..Default::default()
39
})))
40
}
41
42
pub fn insert_asset_text(&self, path: &Path, asset: &str) {
43
self.insert_asset(path, asset.as_bytes().to_vec());
44
}
45
46
pub fn insert_meta_text(&self, path: &Path, asset: &str) {
47
self.insert_meta(path, asset.as_bytes().to_vec());
48
}
49
50
pub fn insert_asset(&self, path: &Path, value: impl Into<Value>) {
51
let mut dir = self.clone();
52
if let Some(parent) = path.parent() {
53
dir = self.get_or_insert_dir(parent);
54
}
55
dir.0
56
.write()
57
.unwrap_or_else(PoisonError::into_inner)
58
.assets
59
.insert(
60
path.file_name().unwrap().to_string_lossy().into(),
61
Data {
62
value: value.into(),
63
path: path.to_owned(),
64
},
65
);
66
}
67
68
/// Removes the stored asset at `path`.
69
///
70
/// Returns the [`Data`] stored if found, [`None`] otherwise.
71
pub fn remove_asset(&self, path: &Path) -> Option<Data> {
72
let mut dir = self.clone();
73
if let Some(parent) = path.parent() {
74
dir = self.get_or_insert_dir(parent);
75
}
76
let key: Box<str> = path.file_name().unwrap().to_string_lossy().into();
77
dir.0
78
.write()
79
.unwrap_or_else(PoisonError::into_inner)
80
.assets
81
.remove(&key)
82
}
83
84
pub fn insert_meta(&self, path: &Path, value: impl Into<Value>) {
85
let mut dir = self.clone();
86
if let Some(parent) = path.parent() {
87
dir = self.get_or_insert_dir(parent);
88
}
89
dir.0
90
.write()
91
.unwrap_or_else(PoisonError::into_inner)
92
.metadata
93
.insert(
94
path.file_name().unwrap().to_string_lossy().into(),
95
Data {
96
value: value.into(),
97
path: path.to_owned(),
98
},
99
);
100
}
101
102
/// Removes the stored metadata at `path`.
103
///
104
/// Returns the [`Data`] stored if found, [`None`] otherwise.
105
pub fn remove_metadata(&self, path: &Path) -> Option<Data> {
106
let mut dir = self.clone();
107
if let Some(parent) = path.parent() {
108
dir = self.get_or_insert_dir(parent);
109
}
110
let key: Box<str> = path.file_name().unwrap().to_string_lossy().into();
111
dir.0
112
.write()
113
.unwrap_or_else(PoisonError::into_inner)
114
.metadata
115
.remove(&key)
116
}
117
118
pub fn get_or_insert_dir(&self, path: &Path) -> Dir {
119
let mut dir = self.clone();
120
let mut full_path = PathBuf::new();
121
for c in path.components() {
122
full_path.push(c);
123
let name = c.as_os_str().to_string_lossy().into();
124
dir = {
125
let dirs = &mut dir.0.write().unwrap_or_else(PoisonError::into_inner).dirs;
126
dirs.entry(name)
127
.or_insert_with(|| Dir::new(full_path.clone()))
128
.clone()
129
};
130
}
131
132
dir
133
}
134
135
/// Removes the dir at `path`.
136
///
137
/// Returns the [`Dir`] stored if found, [`None`] otherwise.
138
pub fn remove_dir(&self, path: &Path) -> Option<Dir> {
139
let mut dir = self.clone();
140
if let Some(parent) = path.parent() {
141
dir = self.get_or_insert_dir(parent);
142
}
143
let key: Box<str> = path.file_name().unwrap().to_string_lossy().into();
144
dir.0
145
.write()
146
.unwrap_or_else(PoisonError::into_inner)
147
.dirs
148
.remove(&key)
149
}
150
151
pub fn get_dir(&self, path: &Path) -> Option<Dir> {
152
let mut dir = self.clone();
153
for p in path.components() {
154
let component = p.as_os_str().to_str().unwrap();
155
let next_dir = dir
156
.0
157
.read()
158
.unwrap_or_else(PoisonError::into_inner)
159
.dirs
160
.get(component)?
161
.clone();
162
dir = next_dir;
163
}
164
Some(dir)
165
}
166
167
pub fn get_asset(&self, path: &Path) -> Option<Data> {
168
let mut dir = self.clone();
169
if let Some(parent) = path.parent() {
170
dir = dir.get_dir(parent)?;
171
}
172
173
path.file_name().and_then(|f| {
174
dir.0
175
.read()
176
.unwrap_or_else(PoisonError::into_inner)
177
.assets
178
.get(f.to_str().unwrap())
179
.cloned()
180
})
181
}
182
183
pub fn get_metadata(&self, path: &Path) -> Option<Data> {
184
let mut dir = self.clone();
185
if let Some(parent) = path.parent() {
186
dir = dir.get_dir(parent)?;
187
}
188
189
path.file_name().and_then(|f| {
190
dir.0
191
.read()
192
.unwrap_or_else(PoisonError::into_inner)
193
.metadata
194
.get(f.to_str().unwrap())
195
.cloned()
196
})
197
}
198
199
pub fn path(&self) -> PathBuf {
200
self.0
201
.read()
202
.unwrap_or_else(PoisonError::into_inner)
203
.path
204
.to_owned()
205
}
206
}
207
208
pub struct DirStream {
209
dir: Dir,
210
index: usize,
211
dir_index: usize,
212
}
213
214
impl DirStream {
215
fn new(dir: Dir) -> Self {
216
Self {
217
dir,
218
index: 0,
219
dir_index: 0,
220
}
221
}
222
}
223
224
impl Stream for DirStream {
225
type Item = PathBuf;
226
227
fn poll_next(
228
self: Pin<&mut Self>,
229
_cx: &mut core::task::Context<'_>,
230
) -> Poll<Option<Self::Item>> {
231
let this = self.get_mut();
232
let dir = this.dir.0.read().unwrap_or_else(PoisonError::into_inner);
233
234
let dir_index = this.dir_index;
235
if let Some(dir_path) = dir
236
.dirs
237
.keys()
238
.nth(dir_index)
239
.map(|d| dir.path.join(d.as_ref()))
240
{
241
this.dir_index += 1;
242
Poll::Ready(Some(dir_path))
243
} else {
244
let index = this.index;
245
this.index += 1;
246
Poll::Ready(dir.assets.values().nth(index).map(|d| d.path().to_owned()))
247
}
248
}
249
}
250
251
/// In-memory [`AssetReader`] implementation.
252
/// This is primarily intended for unit tests.
253
#[derive(Default, Clone)]
254
pub struct MemoryAssetReader {
255
pub root: Dir,
256
}
257
258
/// In-memory [`AssetWriter`] implementation.
259
///
260
/// This is primarily intended for unit tests.
261
#[derive(Default, Clone)]
262
pub struct MemoryAssetWriter {
263
pub root: Dir,
264
}
265
266
/// Asset data stored in a [`Dir`].
267
#[derive(Clone, Debug)]
268
pub struct Data {
269
path: PathBuf,
270
value: Value,
271
}
272
273
/// Stores either an allocated vec of bytes or a static array of bytes.
274
#[derive(Clone, Debug)]
275
pub enum Value {
276
Vec(Arc<Vec<u8>>),
277
Static(&'static [u8]),
278
}
279
280
impl Data {
281
/// The path that this data was written to.
282
pub fn path(&self) -> &Path {
283
&self.path
284
}
285
286
/// The value in bytes that was written here.
287
pub fn value(&self) -> &[u8] {
288
match &self.value {
289
Value::Vec(vec) => vec,
290
Value::Static(value) => value,
291
}
292
}
293
}
294
295
impl From<Vec<u8>> for Value {
296
fn from(value: Vec<u8>) -> Self {
297
Self::Vec(Arc::new(value))
298
}
299
}
300
301
impl From<&'static [u8]> for Value {
302
fn from(value: &'static [u8]) -> Self {
303
Self::Static(value)
304
}
305
}
306
307
impl<const N: usize> From<&'static [u8; N]> for Value {
308
fn from(value: &'static [u8; N]) -> Self {
309
Self::Static(value)
310
}
311
}
312
313
struct DataReader {
314
data: Data,
315
bytes_read: usize,
316
}
317
318
impl AsyncRead for DataReader {
319
fn poll_read(
320
self: Pin<&mut Self>,
321
_cx: &mut core::task::Context<'_>,
322
buf: &mut [u8],
323
) -> Poll<futures_io::Result<usize>> {
324
// Get the mut borrow to avoid trying to borrow the pin itself multiple times.
325
let this = self.get_mut();
326
Poll::Ready(Ok(crate::io::slice_read(
327
this.data.value(),
328
&mut this.bytes_read,
329
buf,
330
)))
331
}
332
}
333
334
impl AsyncSeek for DataReader {
335
fn poll_seek(
336
self: Pin<&mut Self>,
337
_cx: &mut core::task::Context<'_>,
338
pos: SeekFrom,
339
) -> Poll<std::io::Result<u64>> {
340
// Get the mut borrow to avoid trying to borrow the pin itself multiple times.
341
let this = self.get_mut();
342
Poll::Ready(crate::io::slice_seek(
343
this.data.value(),
344
&mut this.bytes_read,
345
pos,
346
))
347
}
348
}
349
350
impl Reader for DataReader {
351
fn read_to_end<'a>(
352
&'a mut self,
353
buf: &'a mut Vec<u8>,
354
) -> stackfuture::StackFuture<'a, std::io::Result<usize>, { super::STACK_FUTURE_SIZE }> {
355
crate::io::read_to_end(self.data.value(), &mut self.bytes_read, buf)
356
}
357
358
fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
359
Ok(self)
360
}
361
}
362
363
impl AssetReader for MemoryAssetReader {
364
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
365
self.root
366
.get_asset(path)
367
.map(|data| DataReader {
368
data,
369
bytes_read: 0,
370
})
371
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
372
}
373
374
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
375
self.root
376
.get_metadata(path)
377
.map(|data| DataReader {
378
data,
379
bytes_read: 0,
380
})
381
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
382
}
383
384
async fn read_directory<'a>(
385
&'a self,
386
path: &'a Path,
387
) -> Result<Box<PathStream>, AssetReaderError> {
388
self.root
389
.get_dir(path)
390
.map(|dir| {
391
let stream: Box<PathStream> = Box::new(DirStream::new(dir));
392
stream
393
})
394
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
395
}
396
397
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
398
Ok(self.root.get_dir(path).is_some())
399
}
400
}
401
402
/// A writer that writes into [`Dir`], buffering internally until flushed/closed.
403
struct DataWriter {
404
/// The dir to write to.
405
dir: Dir,
406
/// The path to write to.
407
path: PathBuf,
408
/// The current buffer of data.
409
///
410
/// This will include data that has been flushed already.
411
current_data: Vec<u8>,
412
/// Whether to write to the data or to the meta.
413
is_meta_writer: bool,
414
}
415
416
impl AsyncWrite for DataWriter {
417
fn poll_write(
418
self: Pin<&mut Self>,
419
_: &mut core::task::Context<'_>,
420
buf: &[u8],
421
) -> Poll<std::io::Result<usize>> {
422
self.get_mut().current_data.extend_from_slice(buf);
423
Poll::Ready(Ok(buf.len()))
424
}
425
426
fn poll_flush(
427
self: Pin<&mut Self>,
428
_: &mut core::task::Context<'_>,
429
) -> Poll<std::io::Result<()>> {
430
// Write the data to our fake disk. This means we will repeatedly reinsert the asset.
431
if self.is_meta_writer {
432
self.dir.insert_meta(&self.path, self.current_data.clone());
433
} else {
434
self.dir.insert_asset(&self.path, self.current_data.clone());
435
}
436
Poll::Ready(Ok(()))
437
}
438
439
fn poll_close(
440
self: Pin<&mut Self>,
441
cx: &mut core::task::Context<'_>,
442
) -> Poll<std::io::Result<()>> {
443
// A flush will just write the data to Dir, which is all we need to do for close.
444
self.poll_flush(cx)
445
}
446
}
447
448
impl AssetWriter for MemoryAssetWriter {
449
async fn write<'a>(&'a self, path: &'a Path) -> Result<Box<super::Writer>, AssetWriterError> {
450
Ok(Box::new(DataWriter {
451
dir: self.root.clone(),
452
path: path.to_owned(),
453
current_data: vec![],
454
is_meta_writer: false,
455
}))
456
}
457
458
async fn write_meta<'a>(
459
&'a self,
460
path: &'a Path,
461
) -> Result<Box<super::Writer>, AssetWriterError> {
462
Ok(Box::new(DataWriter {
463
dir: self.root.clone(),
464
path: path.to_owned(),
465
current_data: vec![],
466
is_meta_writer: true,
467
}))
468
}
469
470
async fn remove<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
471
if self.root.remove_asset(path).is_none() {
472
return Err(AssetWriterError::Io(Error::new(
473
ErrorKind::NotFound,
474
"no such file",
475
)));
476
}
477
Ok(())
478
}
479
480
async fn remove_meta<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
481
self.root.remove_metadata(path);
482
Ok(())
483
}
484
485
async fn rename<'a>(
486
&'a self,
487
old_path: &'a Path,
488
new_path: &'a Path,
489
) -> Result<(), AssetWriterError> {
490
let Some(old_asset) = self.root.get_asset(old_path) else {
491
return Err(AssetWriterError::Io(Error::new(
492
ErrorKind::NotFound,
493
"no such file",
494
)));
495
};
496
self.root.insert_asset(new_path, old_asset.value);
497
// Remove the asset after instead of before since otherwise there'd be a moment where the
498
// Dir is unlocked and missing both the old and new paths. This just prevents race
499
// conditions.
500
self.root.remove_asset(old_path);
501
Ok(())
502
}
503
504
async fn rename_meta<'a>(
505
&'a self,
506
old_path: &'a Path,
507
new_path: &'a Path,
508
) -> Result<(), AssetWriterError> {
509
let Some(old_meta) = self.root.get_metadata(old_path) else {
510
return Err(AssetWriterError::Io(Error::new(
511
ErrorKind::NotFound,
512
"no such file",
513
)));
514
};
515
self.root.insert_meta(new_path, old_meta.value);
516
// Remove the meta after instead of before since otherwise there'd be a moment where the
517
// Dir is unlocked and missing both the old and new paths. This just prevents race
518
// conditions.
519
self.root.remove_metadata(old_path);
520
Ok(())
521
}
522
523
async fn create_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
524
// Just pretend we're on a file system that doesn't consider directory re-creation a
525
// failure.
526
self.root.get_or_insert_dir(path);
527
Ok(())
528
}
529
530
async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
531
if self.root.remove_dir(path).is_none() {
532
return Err(AssetWriterError::Io(Error::new(
533
ErrorKind::NotFound,
534
"no such dir",
535
)));
536
}
537
Ok(())
538
}
539
540
async fn remove_empty_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
541
let Some(dir) = self.root.get_dir(path) else {
542
return Err(AssetWriterError::Io(Error::new(
543
ErrorKind::NotFound,
544
"no such dir",
545
)));
546
};
547
548
let dir = dir.0.read().unwrap();
549
if !dir.assets.is_empty() || !dir.metadata.is_empty() || !dir.dirs.is_empty() {
550
return Err(AssetWriterError::Io(Error::new(
551
ErrorKind::DirectoryNotEmpty,
552
"not empty",
553
)));
554
}
555
556
self.root.remove_dir(path);
557
Ok(())
558
}
559
560
async fn remove_assets_in_directory<'a>(
561
&'a self,
562
path: &'a Path,
563
) -> Result<(), AssetWriterError> {
564
let Some(dir) = self.root.get_dir(path) else {
565
return Err(AssetWriterError::Io(Error::new(
566
ErrorKind::NotFound,
567
"no such dir",
568
)));
569
};
570
571
let mut dir = dir.0.write().unwrap();
572
dir.assets.clear();
573
dir.dirs.clear();
574
dir.metadata.clear();
575
Ok(())
576
}
577
}
578
579
#[cfg(test)]
580
pub mod test {
581
use super::Dir;
582
use std::path::Path;
583
584
#[test]
585
fn memory_dir() {
586
let dir = Dir::default();
587
let a_path = Path::new("a.txt");
588
let a_data = "a".as_bytes().to_vec();
589
let a_meta = "ameta".as_bytes().to_vec();
590
591
dir.insert_asset(a_path, a_data.clone());
592
let asset = dir.get_asset(a_path).unwrap();
593
assert_eq!(asset.path(), a_path);
594
assert_eq!(asset.value(), a_data);
595
596
dir.insert_meta(a_path, a_meta.clone());
597
let meta = dir.get_metadata(a_path).unwrap();
598
assert_eq!(meta.path(), a_path);
599
assert_eq!(meta.value(), a_meta);
600
601
let b_path = Path::new("x/y/b.txt");
602
let b_data = "b".as_bytes().to_vec();
603
let b_meta = "meta".as_bytes().to_vec();
604
dir.insert_asset(b_path, b_data.clone());
605
dir.insert_meta(b_path, b_meta.clone());
606
607
let asset = dir.get_asset(b_path).unwrap();
608
assert_eq!(asset.path(), b_path);
609
assert_eq!(asset.value(), b_data);
610
611
let meta = dir.get_metadata(b_path).unwrap();
612
assert_eq!(meta.path(), b_path);
613
assert_eq!(meta.value(), b_meta);
614
}
615
}
616
617