bevy_asset/io/
mod.rs

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