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