Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/src/io/mod.rs
9395 views
1
#[cfg(all(feature = "file_watcher", target_arch = "wasm32"))]
2
compile_error!(
3
"The \"file_watcher\" feature for hot reloading does not work \
4
on Wasm.\nDisable \"file_watcher\" \
5
when compiling to Wasm"
6
);
7
8
#[cfg(target_os = "android")]
9
pub mod android;
10
pub mod embedded;
11
#[cfg(not(target_arch = "wasm32"))]
12
pub mod file;
13
pub mod memory;
14
pub mod processor_gated;
15
#[cfg(target_arch = "wasm32")]
16
pub mod wasm;
17
#[cfg(any(feature = "http", feature = "https"))]
18
pub mod web;
19
20
#[cfg(test)]
21
pub mod gated;
22
23
mod source;
24
25
pub use futures_lite::AsyncWriteExt;
26
pub use source::*;
27
28
use alloc::{boxed::Box, sync::Arc, vec::Vec};
29
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
30
use core::{
31
mem::size_of,
32
pin::Pin,
33
task::{Context, Poll},
34
};
35
use futures_io::{AsyncRead, AsyncSeek, AsyncWrite};
36
use futures_lite::Stream;
37
use std::{
38
io::SeekFrom,
39
path::{Path, PathBuf},
40
};
41
use thiserror::Error;
42
43
/// Errors that occur while loading assets.
44
#[derive(Error, Debug, Clone)]
45
pub enum AssetReaderError {
46
/// Path not found.
47
#[error("Path not found: {}", _0.display())]
48
NotFound(PathBuf),
49
50
/// Encountered an I/O error while loading an asset.
51
#[error("Encountered an I/O error while loading asset: {0}")]
52
Io(Arc<std::io::Error>),
53
54
/// The HTTP request completed but returned an unhandled [HTTP response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status).
55
/// - If the request returns a 404 error, expect [`AssetReaderError::NotFound`].
56
/// - If the request fails before getting a status code (e.g. request timeout, interrupted connection, etc), expect [`AssetReaderError::Io`].
57
#[error("Encountered HTTP status {0:?} when loading asset")]
58
HttpError(u16),
59
}
60
61
impl PartialEq for AssetReaderError {
62
/// Equality comparison for `AssetReaderError::Io` is not full (only through `ErrorKind` of inner error)
63
#[inline]
64
fn eq(&self, other: &Self) -> bool {
65
match (self, other) {
66
(Self::NotFound(path), Self::NotFound(other_path)) => path == other_path,
67
(Self::Io(error), Self::Io(other_error)) => error.kind() == other_error.kind(),
68
(Self::HttpError(code), Self::HttpError(other_code)) => code == other_code,
69
_ => false,
70
}
71
}
72
}
73
74
impl Eq for AssetReaderError {}
75
76
impl From<std::io::Error> for AssetReaderError {
77
fn from(value: std::io::Error) -> Self {
78
Self::Io(Arc::new(value))
79
}
80
}
81
82
/// The maximum size of a future returned from [`Reader::read_to_end`].
83
/// This is large enough to fit ten references.
84
// Ideally this would be even smaller (ReadToEndFuture only needs space for two references based on its definition),
85
// but compiler optimizations can apparently inflate the stack size of futures due to inlining, which makes
86
// a higher maximum necessary.
87
pub const STACK_FUTURE_SIZE: usize = 10 * size_of::<&()>();
88
89
pub use stackfuture::StackFuture;
90
91
/// A type returned from [`AssetReader::read`], which is used to read the contents of a file
92
/// (or virtual file) corresponding to an asset.
93
///
94
/// This is essentially a trait alias for types implementing [`AsyncRead`] and [`AsyncSeek`].
95
/// The only reason a blanket implementation is not provided for applicable types is to allow
96
/// implementors to override the provided implementation of [`Reader::read_to_end`].
97
pub trait Reader: AsyncRead + Unpin + Send + Sync {
98
/// Reads the entire contents of this reader and appends them to a vec.
99
///
100
/// # Note for implementors
101
/// You should override the provided implementation if you can fill up the buffer more
102
/// efficiently than the default implementation, which calls `poll_read` repeatedly to
103
/// fill up the buffer 32 bytes at a time.
104
fn read_to_end<'a>(
105
&'a mut self,
106
buf: &'a mut Vec<u8>,
107
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
108
let future = futures_lite::AsyncReadExt::read_to_end(self, buf);
109
StackFuture::from(future)
110
}
111
112
/// Casts this [`Reader`] as a [`SeekableReader`], which layers on [`AsyncSeek`] functionality.
113
/// Returns [`Ok`] if this [`Reader`] supports seeking. Otherwise returns [`Err`].
114
///
115
/// Implementers of [`Reader`] are highly encouraged to provide this functionality, as it makes the
116
/// reader compatible with "seeking" [`AssetLoader`](crate::AssetLoader) implementations.
117
///
118
/// [`AssetLoader`](crate::AssetLoader) implementations that call this are encouraged to provide fallback behavior
119
/// when it fails, such as reading into a seek-able [`Vec`] (or [`AsyncSeek`]-able [`VecReader`]):
120
///
121
/// ```
122
/// # use bevy_asset::{io::{VecReader, Reader}, AsyncSeekExt};
123
/// # use std::{io::SeekFrom, vec::Vec};
124
/// # async {
125
/// # let mut original_reader = VecReader::new(Vec::new());
126
/// # let reader: &mut dyn Reader = &mut original_reader;
127
/// let mut fallback_reader;
128
/// let reader = match reader.seekable() {
129
/// Ok(seek) => seek,
130
/// Err(_) => {
131
/// fallback_reader = VecReader::new(Vec::new());
132
/// reader.read_to_end(&mut fallback_reader.bytes).await.unwrap();
133
/// &mut fallback_reader
134
/// }
135
/// };
136
/// reader.seek(SeekFrom::Start(10)).await.unwrap();
137
/// # };
138
/// ```
139
fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError>;
140
}
141
142
/// A [`Reader`] that also has [`AsyncSeek`] functionality.
143
/// See [`Reader::seekable`] for details.
144
pub trait SeekableReader: Reader + AsyncSeek {}
145
146
impl<T: Reader + AsyncSeek> SeekableReader for T {}
147
148
/// Error returned by [`Reader::seekable`] when the reader implementation does not support [`AsyncSeek`] behavior.
149
#[derive(Error, Debug, Copy, Clone)]
150
#[error(
151
"The `Reader` returned by the current `AssetReader` does not support `AsyncSeek` behavior."
152
)]
153
pub struct ReaderNotSeekableError;
154
155
impl Reader for Box<dyn Reader + '_> {
156
fn read_to_end<'a>(
157
&'a mut self,
158
buf: &'a mut Vec<u8>,
159
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
160
(**self).read_to_end(buf)
161
}
162
163
fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
164
(**self).seekable()
165
}
166
}
167
168
/// A future that returns a value or an [`AssetReaderError`]
169
pub trait AssetReaderFuture:
170
ConditionalSendFuture<Output = Result<Self::Value, AssetReaderError>>
171
{
172
type Value;
173
}
174
175
impl<F, T> AssetReaderFuture for F
176
where
177
F: ConditionalSendFuture<Output = Result<T, AssetReaderError>>,
178
{
179
type Value = T;
180
}
181
182
/// Performs read operations on an asset storage. [`AssetReader`] exposes a "virtual filesystem"
183
/// API, where asset bytes and asset metadata bytes are both stored and accessible for a given
184
/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetReader`] instead.
185
///
186
/// This trait defines asset-agnostic mechanisms to read bytes from a storage system.
187
/// For the per-asset-type saving/loading logic, see [`AssetSaver`](crate::saver::AssetSaver) and [`AssetLoader`](crate::loader::AssetLoader).
188
///
189
/// For a complementary version of this trait that can write assets to storage, see [`AssetWriter`].
190
pub trait AssetReader: Send + Sync + 'static {
191
/// Returns a future to load the full file data at the provided path.
192
///
193
/// # Note for implementors
194
/// The preferred style for implementing this method is an `async fn` returning an opaque type.
195
///
196
/// ```no_run
197
/// # use std::path::Path;
198
/// # use bevy_asset::{prelude::*, io::{AssetReader, PathStream, Reader, AssetReaderError}};
199
/// # struct MyReader;
200
/// impl AssetReader for MyReader {
201
/// async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
202
/// // ...
203
/// # let val: Box<dyn Reader> = unimplemented!(); Ok(val)
204
/// }
205
/// # async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
206
/// # let val: Box<dyn Reader> = unimplemented!(); Ok(val) }
207
/// # async fn read_directory<'a>(&'a self, path: &'a Path) -> Result<Box<PathStream>, AssetReaderError> { unimplemented!() }
208
/// # async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> { unimplemented!() }
209
/// # async fn read_meta_bytes<'a>(&'a self, path: &'a Path) -> Result<Vec<u8>, AssetReaderError> { unimplemented!() }
210
/// }
211
/// ```
212
fn read<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
213
/// Returns a future to load the full file data at the provided path.
214
fn read_meta<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
215
/// Returns an iterator of directory entry names at the provided path.
216
fn read_directory<'a>(
217
&'a self,
218
path: &'a Path,
219
) -> impl ConditionalSendFuture<Output = Result<Box<PathStream>, AssetReaderError>>;
220
/// Returns true if the provided path points to a directory.
221
fn is_directory<'a>(
222
&'a self,
223
path: &'a Path,
224
) -> impl ConditionalSendFuture<Output = Result<bool, AssetReaderError>>;
225
/// Reads asset metadata bytes at the given `path` into a [`Vec<u8>`]. This is a convenience
226
/// function that wraps [`AssetReader::read_meta`] by default.
227
fn read_meta_bytes<'a>(
228
&'a self,
229
path: &'a Path,
230
) -> impl ConditionalSendFuture<Output = Result<Vec<u8>, AssetReaderError>> {
231
async {
232
let mut meta_reader = self.read_meta(path).await?;
233
let mut meta_bytes = Vec::new();
234
meta_reader.read_to_end(&mut meta_bytes).await?;
235
Ok(meta_bytes)
236
}
237
}
238
}
239
240
/// Equivalent to an [`AssetReader`] but using boxed futures, necessary eg. when using a `dyn AssetReader`,
241
/// as [`AssetReader`] isn't currently object safe.
242
pub trait ErasedAssetReader: Send + Sync + 'static {
243
/// Returns a future to load the full file data at the provided path.
244
fn read<'a>(
245
&'a self,
246
path: &'a Path,
247
) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
248
/// Returns a future to load the full file data at the provided path.
249
fn read_meta<'a>(
250
&'a self,
251
path: &'a Path,
252
) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
253
/// Returns an iterator of directory entry names at the provided path.
254
fn read_directory<'a>(
255
&'a self,
256
path: &'a Path,
257
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>>;
258
/// Returns true if the provided path points to a directory.
259
fn is_directory<'a>(
260
&'a self,
261
path: &'a Path,
262
) -> BoxedFuture<'a, Result<bool, AssetReaderError>>;
263
/// Reads asset metadata bytes at the given `path` into a [`Vec<u8>`]. This is a convenience
264
/// function that wraps [`ErasedAssetReader::read_meta`] by default.
265
fn read_meta_bytes<'a>(
266
&'a self,
267
path: &'a Path,
268
) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>>;
269
}
270
271
impl<T: AssetReader> ErasedAssetReader for T {
272
fn read<'a>(
273
&'a self,
274
path: &'a Path,
275
) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
276
Box::pin(async move {
277
let reader = Self::read(self, path).await?;
278
Ok(Box::new(reader) as Box<dyn Reader>)
279
})
280
}
281
fn read_meta<'a>(
282
&'a self,
283
path: &'a Path,
284
) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
285
Box::pin(async {
286
let reader = Self::read_meta(self, path).await?;
287
Ok(Box::new(reader) as Box<dyn Reader>)
288
})
289
}
290
fn read_directory<'a>(
291
&'a self,
292
path: &'a Path,
293
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
294
Box::pin(Self::read_directory(self, path))
295
}
296
fn is_directory<'a>(
297
&'a self,
298
path: &'a Path,
299
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
300
Box::pin(Self::is_directory(self, path))
301
}
302
fn read_meta_bytes<'a>(
303
&'a self,
304
path: &'a Path,
305
) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>> {
306
Box::pin(Self::read_meta_bytes(self, path))
307
}
308
}
309
310
pub type Writer = dyn AsyncWrite + Unpin + Send + Sync;
311
312
pub type PathStream = dyn Stream<Item = PathBuf> + Unpin + Send;
313
314
/// Errors that occur while loading assets.
315
#[derive(Error, Debug)]
316
pub enum AssetWriterError {
317
/// Encountered an I/O error while loading an asset.
318
#[error("encountered an io error while loading asset: {0}")]
319
Io(#[from] std::io::Error),
320
}
321
322
/// Preforms write operations on an asset storage. [`AssetWriter`] exposes a "virtual filesystem"
323
/// API, where asset bytes and asset metadata bytes are both stored and accessible for a given
324
/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetWriter`] instead.
325
///
326
/// This trait defines asset-agnostic mechanisms to write bytes to a storage system.
327
/// For the per-asset-type saving/loading logic, see [`AssetSaver`](crate::saver::AssetSaver) and [`AssetLoader`](crate::loader::AssetLoader).
328
///
329
/// For a complementary version of this trait that can read assets from storage, see [`AssetReader`].
330
pub trait AssetWriter: Send + Sync + 'static {
331
/// Writes the full asset bytes at the provided path.
332
fn write<'a>(
333
&'a self,
334
path: &'a Path,
335
) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
336
/// Writes the full asset meta bytes at the provided path.
337
/// This _should not_ include storage specific extensions like `.meta`.
338
fn write_meta<'a>(
339
&'a self,
340
path: &'a Path,
341
) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
342
/// Removes the asset stored at the given path.
343
fn remove<'a>(
344
&'a self,
345
path: &'a Path,
346
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
347
/// Removes the asset meta stored at the given path.
348
/// This _should not_ include storage specific extensions like `.meta`.
349
fn remove_meta<'a>(
350
&'a self,
351
path: &'a Path,
352
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
353
/// Renames the asset at `old_path` to `new_path`
354
fn rename<'a>(
355
&'a self,
356
old_path: &'a Path,
357
new_path: &'a Path,
358
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
359
/// Renames the asset meta for the asset at `old_path` to `new_path`.
360
/// This _should not_ include storage specific extensions like `.meta`.
361
fn rename_meta<'a>(
362
&'a self,
363
old_path: &'a Path,
364
new_path: &'a Path,
365
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
366
/// Creates a directory at the given path, including all parent directories if they do not
367
/// already exist.
368
fn create_directory<'a>(
369
&'a self,
370
path: &'a Path,
371
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
372
/// Removes the directory at the given path, including all assets _and_ directories in that directory.
373
fn remove_directory<'a>(
374
&'a self,
375
path: &'a Path,
376
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
377
/// Removes the directory at the given path, but only if it is completely empty. This will return an error if the
378
/// directory is not empty.
379
fn remove_empty_directory<'a>(
380
&'a self,
381
path: &'a Path,
382
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
383
/// Removes all assets (and directories) in this directory, resulting in an empty directory.
384
fn remove_assets_in_directory<'a>(
385
&'a self,
386
path: &'a Path,
387
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
388
/// Writes the asset `bytes` to the given `path`.
389
fn write_bytes<'a>(
390
&'a self,
391
path: &'a Path,
392
bytes: &'a [u8],
393
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
394
async {
395
let mut writer = self.write(path).await?;
396
writer.write_all(bytes).await?;
397
writer.flush().await?;
398
Ok(())
399
}
400
}
401
/// Writes the asset meta `bytes` to the given `path`.
402
fn write_meta_bytes<'a>(
403
&'a self,
404
path: &'a Path,
405
bytes: &'a [u8],
406
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
407
async {
408
let mut meta_writer = self.write_meta(path).await?;
409
meta_writer.write_all(bytes).await?;
410
meta_writer.flush().await?;
411
Ok(())
412
}
413
}
414
}
415
416
/// Equivalent to an [`AssetWriter`] but using boxed futures, necessary eg. when using a `dyn AssetWriter`,
417
/// as [`AssetWriter`] isn't currently object safe.
418
pub trait ErasedAssetWriter: Send + Sync + 'static {
419
/// Writes the full asset bytes at the provided path.
420
fn write<'a>(
421
&'a self,
422
path: &'a Path,
423
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
424
/// Writes the full asset meta bytes at the provided path.
425
/// This _should not_ include storage specific extensions like `.meta`.
426
fn write_meta<'a>(
427
&'a self,
428
path: &'a Path,
429
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
430
/// Removes the asset stored at the given path.
431
fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
432
/// Removes the asset meta stored at the given path.
433
/// This _should not_ include storage specific extensions like `.meta`.
434
fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
435
/// Renames the asset at `old_path` to `new_path`
436
fn rename<'a>(
437
&'a self,
438
old_path: &'a Path,
439
new_path: &'a Path,
440
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
441
/// Renames the asset meta for the asset at `old_path` to `new_path`.
442
/// This _should not_ include storage specific extensions like `.meta`.
443
fn rename_meta<'a>(
444
&'a self,
445
old_path: &'a Path,
446
new_path: &'a Path,
447
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
448
/// Creates a directory at the given path, including all parent directories if they do not
449
/// already exist.
450
fn create_directory<'a>(
451
&'a self,
452
path: &'a Path,
453
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
454
/// Removes the directory at the given path, including all assets _and_ directories in that directory.
455
fn remove_directory<'a>(
456
&'a self,
457
path: &'a Path,
458
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
459
/// Removes the directory at the given path, but only if it is completely empty. This will return an error if the
460
/// directory is not empty.
461
fn remove_empty_directory<'a>(
462
&'a self,
463
path: &'a Path,
464
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
465
/// Removes all assets (and directories) in this directory, resulting in an empty directory.
466
fn remove_assets_in_directory<'a>(
467
&'a self,
468
path: &'a Path,
469
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
470
/// Writes the asset `bytes` to the given `path`.
471
fn write_bytes<'a>(
472
&'a self,
473
path: &'a Path,
474
bytes: &'a [u8],
475
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
476
/// Writes the asset meta `bytes` to the given `path`.
477
fn write_meta_bytes<'a>(
478
&'a self,
479
path: &'a Path,
480
bytes: &'a [u8],
481
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
482
}
483
484
impl<T: AssetWriter> ErasedAssetWriter for T {
485
fn write<'a>(
486
&'a self,
487
path: &'a Path,
488
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
489
Box::pin(Self::write(self, path))
490
}
491
fn write_meta<'a>(
492
&'a self,
493
path: &'a Path,
494
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
495
Box::pin(Self::write_meta(self, path))
496
}
497
fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
498
Box::pin(Self::remove(self, path))
499
}
500
fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
501
Box::pin(Self::remove_meta(self, path))
502
}
503
fn rename<'a>(
504
&'a self,
505
old_path: &'a Path,
506
new_path: &'a Path,
507
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
508
Box::pin(Self::rename(self, old_path, new_path))
509
}
510
fn rename_meta<'a>(
511
&'a self,
512
old_path: &'a Path,
513
new_path: &'a Path,
514
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
515
Box::pin(Self::rename_meta(self, old_path, new_path))
516
}
517
fn create_directory<'a>(
518
&'a self,
519
path: &'a Path,
520
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
521
Box::pin(Self::create_directory(self, path))
522
}
523
fn remove_directory<'a>(
524
&'a self,
525
path: &'a Path,
526
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
527
Box::pin(Self::remove_directory(self, path))
528
}
529
fn remove_empty_directory<'a>(
530
&'a self,
531
path: &'a Path,
532
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
533
Box::pin(Self::remove_empty_directory(self, path))
534
}
535
fn remove_assets_in_directory<'a>(
536
&'a self,
537
path: &'a Path,
538
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
539
Box::pin(Self::remove_assets_in_directory(self, path))
540
}
541
fn write_bytes<'a>(
542
&'a self,
543
path: &'a Path,
544
bytes: &'a [u8],
545
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
546
Box::pin(Self::write_bytes(self, path, bytes))
547
}
548
fn write_meta_bytes<'a>(
549
&'a self,
550
path: &'a Path,
551
bytes: &'a [u8],
552
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
553
Box::pin(Self::write_meta_bytes(self, path, bytes))
554
}
555
}
556
557
/// An "asset source change event" that occurs whenever asset (or asset metadata) is created/added/removed
558
#[derive(Clone, Debug, PartialEq, Eq)]
559
pub enum AssetSourceEvent {
560
/// An asset at this path was added.
561
AddedAsset(PathBuf),
562
/// An asset at this path was modified.
563
ModifiedAsset(PathBuf),
564
/// An asset at this path was removed.
565
RemovedAsset(PathBuf),
566
/// An asset at this path was renamed.
567
RenamedAsset { old: PathBuf, new: PathBuf },
568
/// Asset metadata at this path was added.
569
AddedMeta(PathBuf),
570
/// Asset metadata at this path was modified.
571
ModifiedMeta(PathBuf),
572
/// Asset metadata at this path was removed.
573
RemovedMeta(PathBuf),
574
/// Asset metadata at this path was renamed.
575
RenamedMeta { old: PathBuf, new: PathBuf },
576
/// A folder at the given path was added.
577
AddedFolder(PathBuf),
578
/// A folder at the given path was removed.
579
RemovedFolder(PathBuf),
580
/// A folder at the given path was renamed.
581
RenamedFolder { old: PathBuf, new: PathBuf },
582
/// Something of unknown type was removed. It is the job of the event handler to determine the type.
583
/// This exists because notify-rs produces "untyped" rename events without destination paths for unwatched folders, so we can't determine the type of
584
/// the rename.
585
RemovedUnknown {
586
/// The path of the removed asset or folder (undetermined). This could be an asset path or a folder. This will not be a "meta file" path.
587
path: PathBuf,
588
/// This field is only relevant if `path` is determined to be an asset path (and therefore not a folder). If this field is `true`,
589
/// then this event corresponds to a meta removal (not an asset removal) . If `false`, then this event corresponds to an asset removal
590
/// (not a meta removal).
591
is_meta: bool,
592
},
593
}
594
595
/// A handle to an "asset watcher" process, that will listen for and emit [`AssetSourceEvent`] values for as long as
596
/// [`AssetWatcher`] has not been dropped.
597
pub trait AssetWatcher: Send + Sync + 'static {}
598
599
/// An [`AsyncRead`] implementation capable of reading a [`Vec<u8>`].
600
pub struct VecReader {
601
/// The bytes being read. This is the full original list of bytes.
602
pub bytes: Vec<u8>,
603
bytes_read: usize,
604
}
605
606
impl VecReader {
607
/// Create a new [`VecReader`] for `bytes`.
608
pub fn new(bytes: Vec<u8>) -> Self {
609
Self {
610
bytes_read: 0,
611
bytes,
612
}
613
}
614
}
615
616
impl AsyncRead for VecReader {
617
fn poll_read(
618
self: Pin<&mut Self>,
619
_cx: &mut Context<'_>,
620
buf: &mut [u8],
621
) -> Poll<futures_io::Result<usize>> {
622
// Get the mut borrow to avoid trying to borrow the pin itself multiple times.
623
let this = self.get_mut();
624
Poll::Ready(Ok(slice_read(&this.bytes, &mut this.bytes_read, buf)))
625
}
626
}
627
628
impl AsyncSeek for VecReader {
629
fn poll_seek(
630
self: Pin<&mut Self>,
631
_cx: &mut Context<'_>,
632
pos: SeekFrom,
633
) -> Poll<std::io::Result<u64>> {
634
// Get the mut borrow to avoid trying to borrow the pin itself multiple times.
635
let this = self.get_mut();
636
Poll::Ready(slice_seek(&this.bytes, &mut this.bytes_read, pos))
637
}
638
}
639
640
impl Reader for VecReader {
641
fn read_to_end<'a>(
642
&'a mut self,
643
buf: &'a mut Vec<u8>,
644
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
645
read_to_end(&self.bytes, &mut self.bytes_read, buf)
646
}
647
648
fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
649
Ok(self)
650
}
651
}
652
653
/// An [`AsyncRead`] implementation capable of reading a [`&[u8]`].
654
pub struct SliceReader<'a> {
655
bytes: &'a [u8],
656
bytes_read: usize,
657
}
658
659
impl<'a> SliceReader<'a> {
660
/// Create a new [`SliceReader`] for `bytes`.
661
pub fn new(bytes: &'a [u8]) -> Self {
662
Self {
663
bytes,
664
bytes_read: 0,
665
}
666
}
667
}
668
669
impl<'a> AsyncRead for SliceReader<'a> {
670
fn poll_read(
671
mut self: Pin<&mut Self>,
672
_cx: &mut Context<'_>,
673
buf: &mut [u8],
674
) -> Poll<std::io::Result<usize>> {
675
Poll::Ready(Ok(slice_read(self.bytes, &mut self.bytes_read, buf)))
676
}
677
}
678
679
impl<'a> AsyncSeek for SliceReader<'a> {
680
fn poll_seek(
681
mut self: Pin<&mut Self>,
682
_cx: &mut Context<'_>,
683
pos: SeekFrom,
684
) -> Poll<std::io::Result<u64>> {
685
Poll::Ready(slice_seek(self.bytes, &mut self.bytes_read, pos))
686
}
687
}
688
689
impl Reader for SliceReader<'_> {
690
fn read_to_end<'a>(
691
&'a mut self,
692
buf: &'a mut Vec<u8>,
693
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
694
read_to_end(self.bytes, &mut self.bytes_read, buf)
695
}
696
697
fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
698
Ok(self)
699
}
700
}
701
702
/// Performs a read from the `slice` into `buf`.
703
pub(crate) fn slice_read(slice: &[u8], bytes_read: &mut usize, buf: &mut [u8]) -> usize {
704
if *bytes_read >= slice.len() {
705
0
706
} else {
707
let n = std::io::Read::read(&mut &slice[(*bytes_read)..], buf).unwrap();
708
*bytes_read += n;
709
n
710
}
711
}
712
713
/// Performs a "seek" and updates the cursor of `bytes_read`. Returns the new byte position.
714
pub(crate) fn slice_seek(
715
slice: &[u8],
716
bytes_read: &mut usize,
717
pos: SeekFrom,
718
) -> std::io::Result<u64> {
719
let make_error = || {
720
Err(std::io::Error::new(
721
std::io::ErrorKind::InvalidInput,
722
"seek position is out of range",
723
))
724
};
725
let (origin, offset) = match pos {
726
SeekFrom::Current(offset) => (*bytes_read, Ok(offset)),
727
SeekFrom::Start(offset) => (0, offset.try_into()),
728
SeekFrom::End(offset) => (slice.len(), Ok(offset)),
729
};
730
let Ok(offset) = offset else {
731
return make_error();
732
};
733
let Ok(origin): Result<i64, _> = origin.try_into() else {
734
return make_error();
735
};
736
let Ok(new_pos) = (origin + offset).try_into() else {
737
return make_error();
738
};
739
*bytes_read = new_pos;
740
Ok(new_pos as _)
741
}
742
743
/// Copies bytes from source to dest, keeping track of where in the source it starts copying from.
744
///
745
/// This is effectively the impl for [`SliceReader::read_to_end`], but this is provided here so the
746
/// lifetimes are only tied to the buffer and not the [`SliceReader`] itself.
747
pub(crate) fn read_to_end<'a>(
748
source: &'a [u8],
749
bytes_read: &'a mut usize,
750
dest: &'a mut Vec<u8>,
751
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
752
StackFuture::from(async {
753
if *bytes_read >= source.len() {
754
Ok(0)
755
} else {
756
dest.extend_from_slice(&source[*bytes_read..]);
757
let n = source.len() - *bytes_read;
758
*bytes_read = source.len();
759
Ok(n)
760
}
761
})
762
}
763
764
/// Appends `.meta` to the given path:
765
/// - `foo` becomes `foo.meta`
766
/// - `foo.bar` becomes `foo.bar.meta`
767
pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
768
let mut meta_path = path.to_path_buf();
769
let mut extension = path.extension().unwrap_or_default().to_os_string();
770
if !extension.is_empty() {
771
extension.push(".");
772
}
773
extension.push("meta");
774
meta_path.set_extension(extension);
775
meta_path
776
}
777
778
#[cfg(any(target_arch = "wasm32", target_os = "android"))]
779
/// A [`PathBuf`] [`Stream`] implementation that immediately returns nothing.
780
struct EmptyPathStream;
781
782
#[cfg(any(target_arch = "wasm32", target_os = "android"))]
783
impl Stream for EmptyPathStream {
784
type Item = PathBuf;
785
786
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
787
Poll::Ready(None)
788
}
789
}
790
791
#[cfg(test)]
792
mod tests {
793
use super::*;
794
795
#[test]
796
fn get_meta_path_no_extension() {
797
assert_eq!(
798
get_meta_path(Path::new("foo")).to_str().unwrap(),
799
"foo.meta"
800
);
801
}
802
803
#[test]
804
fn get_meta_path_with_extension() {
805
assert_eq!(
806
get_meta_path(Path::new("foo.bar")).to_str().unwrap(),
807
"foo.bar.meta"
808
);
809
}
810
}
811
812