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