bevy/crates/bevy_asset/src/io/mod.rs
Joseph 5876352206
Optimize common usages of AssetReader (#14082)
# Objective

The `AssetReader` trait allows customizing the behavior of fetching
bytes for an `AssetPath`, and expects implementors to return `dyn
AsyncRead + AsyncSeek`. This gives implementors of `AssetLoader` great
flexibility to tightly integrate their asset loading behavior with the
asynchronous task system.

However, almost all implementors of `AssetLoader` don't use the async
functionality at all, and just call `AsyncReadExt::read_to_end(&mut
Vec<u8>)`. This is incredibly inefficient, as this method repeatedly
calls `poll_read` on the trait object, filling the vector 32 bytes at a
time. At my work we have assets that are hundreds of megabytes which
makes this a meaningful overhead.

## Solution

Turn the `Reader` type alias into an actual trait, with a provided
method `read_to_end`. This provided method should be more efficient than
the existing extension method, as the compiler will know the underlying
type of `Reader` when generating this function, which removes the
repeated dynamic dispatches and allows the compiler to make further
optimizations after inlining. Individual implementors are able to
override the provided implementation -- for simple asset readers that
just copy bytes from one buffer to another, this allows removing a large
amount of overhead from the provided implementation.

Now that `Reader` is an actual trait, I also improved the ergonomics for
implementing `AssetReader`. Currently, implementors are expected to box
their reader and return it as a trait object, which adds unnecessary
boilerplate to implementations. This PR changes that trait method to
return a pseudo trait alias, which allows implementors to return `impl
Reader` instead of `Box<dyn Reader>`. Now, the boilerplate for boxing
occurs in `ErasedAssetReader`.

## Testing

I made identical changes to my company's fork of bevy. Our app, which
makes heavy use of `read_to_end` for asset loading, still worked
properly after this. I am not aware if we have a more systematic way of
testing asset loading for correctness.

---

## Migration Guide

The trait method `bevy_asset::io::AssetReader::read` (and `read_meta`)
now return an opaque type instead of a boxed trait object. Implementors
of these methods should change the type signatures appropriately

```rust
impl AssetReader for MyReader {
    // Before
    async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
        let reader = // construct a reader
        Box::new(reader) as Box<Reader<'a>>
    }

    // After
    async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
        // create a reader
    }
}
```

`bevy::asset::io::Reader` is now a trait, rather than a type alias for a
trait object. Implementors of `AssetLoader::load` will need to adjust
the method signature accordingly

```rust
impl AssetLoader for MyLoader {
    async fn load<'a>(
        &'a self,
        // Before:
        reader: &'a mut bevy::asset::io::Reader,
        // After:
        reader: &'a mut dyn bevy::asset::io::Reader,
        _: &'a Self::Settings,
        load_context: &'a mut LoadContext<'_>,
    ) -> Result<Self::Asset, Self::Error> {
}
```

Additionally, implementors of `AssetReader` that return a type
implementing `futures_io::AsyncRead` and `AsyncSeek` might need to
explicitly implement `bevy::asset::io::Reader` for that type.

```rust
impl bevy::asset::io::Reader for MyAsyncReadAndSeek {}
```
2024-07-01 19:59:42 +00:00

696 lines
25 KiB
Rust

#[cfg(all(feature = "file_watcher", target_arch = "wasm32"))]
compile_error!(
"The \"file_watcher\" feature for hot reloading does not work \
on WASM.\nDisable \"file_watcher\" \
when compiling to WASM"
);
#[cfg(target_os = "android")]
pub mod android;
pub mod embedded;
#[cfg(not(target_arch = "wasm32"))]
pub mod file;
pub mod gated;
pub mod memory;
pub mod processor_gated;
#[cfg(target_arch = "wasm32")]
pub mod wasm;
mod source;
pub use futures_lite::AsyncWriteExt;
pub use source::*;
use bevy_utils::{BoxedFuture, ConditionalSendFuture};
use futures_io::{AsyncRead, AsyncSeek, AsyncWrite};
use futures_lite::{ready, Stream};
use std::io::SeekFrom;
use std::task::Context;
use std::{
path::{Path, PathBuf},
pin::Pin,
sync::Arc,
task::Poll,
};
use thiserror::Error;
/// Errors that occur while loading assets.
#[derive(Error, Debug, Clone)]
pub enum AssetReaderError {
/// Path not found.
#[error("Path not found: {0}")]
NotFound(PathBuf),
/// Encountered an I/O error while loading an asset.
#[error("Encountered an I/O error while loading asset: {0}")]
Io(Arc<std::io::Error>),
/// The HTTP request completed but returned an unhandled [HTTP response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status).
/// If the request fails before getting a status code (e.g. request timeout, interrupted connection, etc), expect [`AssetReaderError::Io`].
#[error("Encountered HTTP status {0:?} when loading asset")]
HttpError(u16),
}
impl PartialEq for AssetReaderError {
/// Equality comparison for `AssetReaderError::Io` is not full (only through `ErrorKind` of inner error)
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::NotFound(path), Self::NotFound(other_path)) => path == other_path,
(Self::Io(error), Self::Io(other_error)) => error.kind() == other_error.kind(),
(Self::HttpError(code), Self::HttpError(other_code)) => code == other_code,
_ => false,
}
}
}
impl Eq for AssetReaderError {}
impl From<std::io::Error> for AssetReaderError {
fn from(value: std::io::Error) -> Self {
Self::Io(Arc::new(value))
}
}
/// The maximum size of a future returned from [`Reader::read_to_end`].
/// This is large enough to fit ten references.
// Ideally this would be even smaller (ReadToEndFuture only needs space for two references based on its definition),
// but compiler optimizations can apparently inflate the stack size of futures due to inlining, which makes
// a higher maximum necessary.
pub const STACK_FUTURE_SIZE: usize = 10 * std::mem::size_of::<&()>();
pub use stackfuture::StackFuture;
/// A type returned from [`AssetReader::read`], which is used to read the contents of a file
/// (or virtual file) corresponding to an asset.
///
/// This is essentially a trait alias for types implementing [`AsyncRead`] and [`AsyncSeek`].
/// The only reason a blanket implementation is not provided for applicable types is to allow
/// implementors to override the provided implementation of [`Reader::read_to_end`].
pub trait Reader: AsyncRead + AsyncSeek + Unpin + Send + Sync {
/// Reads the entire contents of this reader and appends them to a vec.
///
/// # Note for implementors
/// You should override the provided implementation if you can fill up the buffer more
/// efficiently than the default implementation, which calls `poll_read` repeatedly to
/// fill up the buffer 32 bytes at a time.
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
let future = futures_lite::AsyncReadExt::read_to_end(self, buf);
StackFuture::from(future)
}
}
impl Reader for Box<dyn Reader + '_> {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
(**self).read_to_end(buf)
}
}
/// A future that returns a value or an [`AssetReaderError`]
pub trait AssetReaderFuture:
ConditionalSendFuture<Output = Result<Self::Value, AssetReaderError>>
{
type Value;
}
impl<F, T> AssetReaderFuture for F
where
F: ConditionalSendFuture<Output = Result<T, AssetReaderError>>,
{
type Value = T;
}
/// Performs read operations on an asset storage. [`AssetReader`] exposes a "virtual filesystem"
/// API, where asset bytes and asset metadata bytes are both stored and accessible for a given
/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetReader`] instead.
///
/// Also see [`AssetWriter`].
pub trait AssetReader: Send + Sync + 'static {
/// Returns a future to load the full file data at the provided path.
///
/// # Note for implementors
/// The preferred style for implementing this method is an `async fn` returning an opaque type.
///
/// ```no_run
/// # use std::path::Path;
/// # use bevy_asset::{prelude::*, io::{AssetReader, PathStream, Reader, AssetReaderError}};
/// # struct MyReader;
/// impl AssetReader for MyReader {
/// async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
/// // ...
/// # let val: Box<dyn Reader> = unimplemented!(); Ok(val)
/// }
/// # async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
/// # let val: Box<dyn Reader> = unimplemented!(); Ok(val) }
/// # async fn read_directory<'a>(&'a self, path: &'a Path) -> Result<Box<PathStream>, AssetReaderError> { unimplemented!() }
/// # async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> { unimplemented!() }
/// # async fn read_meta_bytes<'a>(&'a self, path: &'a Path) -> Result<Vec<u8>, AssetReaderError> { unimplemented!() }
/// }
/// ```
fn read<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
/// Returns a future to load the full file data at the provided path.
fn read_meta<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
/// Returns an iterator of directory entry names at the provided path.
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<Box<PathStream>, AssetReaderError>>;
/// Returns true if the provided path points to a directory.
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<bool, AssetReaderError>>;
/// Reads asset metadata bytes at the given `path` into a [`Vec<u8>`]. This is a convenience
/// function that wraps [`AssetReader::read_meta`] by default.
fn read_meta_bytes<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<Vec<u8>, AssetReaderError>> {
async {
let mut meta_reader = self.read_meta(path).await?;
let mut meta_bytes = Vec::new();
meta_reader.read_to_end(&mut meta_bytes).await?;
Ok(meta_bytes)
}
}
}
/// Equivalent to an [`AssetReader`] but using boxed futures, necessary eg. when using a `dyn AssetReader`,
/// as [`AssetReader`] isn't currently object safe.
pub trait ErasedAssetReader: Send + Sync + 'static {
/// Returns a future to load the full file data at the provided path.
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<dyn Reader + 'a>, AssetReaderError>>;
/// Returns a future to load the full file data at the provided path.
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<dyn Reader + 'a>, AssetReaderError>>;
/// Returns an iterator of directory entry names at the provided path.
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<PathStream>, AssetReaderError>>;
/// Returns true if the provided path points to a directory.
fn is_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<bool, AssetReaderError>>;
/// Reads asset metadata bytes at the given `path` into a [`Vec<u8>`]. This is a convenience
/// function that wraps [`ErasedAssetReader::read_meta`] by default.
fn read_meta_bytes<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Vec<u8>, AssetReaderError>>;
}
impl<T: AssetReader> ErasedAssetReader for T {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<dyn Reader + 'a>, AssetReaderError>> {
Box::pin(async {
let reader = Self::read(self, path).await?;
Ok(Box::new(reader) as Box<dyn Reader>)
})
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<dyn Reader + 'a>, AssetReaderError>> {
Box::pin(async {
let reader = Self::read_meta(self, path).await?;
Ok(Box::new(reader) as Box<dyn Reader>)
})
}
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<PathStream>, AssetReaderError>> {
Box::pin(Self::read_directory(self, path))
}
fn is_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<bool, AssetReaderError>> {
Box::pin(Self::is_directory(self, path))
}
fn read_meta_bytes<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Vec<u8>, AssetReaderError>> {
Box::pin(Self::read_meta_bytes(self, path))
}
}
pub type Writer = dyn AsyncWrite + Unpin + Send + Sync;
pub type PathStream = dyn Stream<Item = PathBuf> + Unpin + Send;
/// Errors that occur while loading assets.
#[derive(Error, Debug)]
pub enum AssetWriterError {
/// Encountered an I/O error while loading an asset.
#[error("encountered an io error while loading asset: {0}")]
Io(#[from] std::io::Error),
}
/// Preforms write operations on an asset storage. [`AssetWriter`] exposes a "virtual filesystem"
/// API, where asset bytes and asset metadata bytes are both stored and accessible for a given
/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetWriter`] instead.
///
/// Also see [`AssetReader`].
pub trait AssetWriter: Send + Sync + 'static {
/// Writes the full asset bytes at the provided path.
fn write<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
/// Writes the full asset meta bytes at the provided path.
/// This _should not_ include storage specific extensions like `.meta`.
fn write_meta<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
/// Removes the asset stored at the given path.
fn remove<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Removes the asset meta stored at the given path.
/// This _should not_ include storage specific extensions like `.meta`.
fn remove_meta<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Renames the asset at `old_path` to `new_path`
fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Renames the asset meta for the asset at `old_path` to `new_path`.
/// This _should not_ include storage specific extensions like `.meta`.
fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Removes the directory at the given path, including all assets _and_ directories in that directory.
fn remove_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Removes the directory at the given path, but only if it is completely empty. This will return an error if the
/// directory is not empty.
fn remove_empty_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Removes all assets (and directories) in this directory, resulting in an empty directory.
fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Writes the asset `bytes` to the given `path`.
fn write_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
async {
let mut writer = self.write(path).await?;
writer.write_all(bytes).await?;
writer.flush().await?;
Ok(())
}
}
/// Writes the asset meta `bytes` to the given `path`.
fn write_meta_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
async {
let mut meta_writer = self.write_meta(path).await?;
meta_writer.write_all(bytes).await?;
meta_writer.flush().await?;
Ok(())
}
}
}
/// Equivalent to an [`AssetWriter`] but using boxed futures, necessary eg. when using a `dyn AssetWriter`,
/// as [`AssetWriter`] isn't currently object safe.
pub trait ErasedAssetWriter: Send + Sync + 'static {
/// Writes the full asset bytes at the provided path.
fn write<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<Box<Writer>, AssetWriterError>>;
/// Writes the full asset meta bytes at the provided path.
/// This _should not_ include storage specific extensions like `.meta`.
fn write_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<Writer>, AssetWriterError>>;
/// Removes the asset stored at the given path.
fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<(), AssetWriterError>>;
/// Removes the asset meta stored at the given path.
/// This _should not_ include storage specific extensions like `.meta`.
fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<(), AssetWriterError>>;
/// Renames the asset at `old_path` to `new_path`
fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>>;
/// Renames the asset meta for the asset at `old_path` to `new_path`.
/// This _should not_ include storage specific extensions like `.meta`.
fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>>;
/// Removes the directory at the given path, including all assets _and_ directories in that directory.
fn remove_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<(), AssetWriterError>>;
/// Removes the directory at the given path, but only if it is completely empty. This will return an error if the
/// directory is not empty.
fn remove_empty_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>>;
/// Removes all assets (and directories) in this directory, resulting in an empty directory.
fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>>;
/// Writes the asset `bytes` to the given `path`.
fn write_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<Result<(), AssetWriterError>>;
/// Writes the asset meta `bytes` to the given `path`.
fn write_meta_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<Result<(), AssetWriterError>>;
}
impl<T: AssetWriter> ErasedAssetWriter for T {
fn write<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<Box<Writer>, AssetWriterError>> {
Box::pin(Self::write(self, path))
}
fn write_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<Writer>, AssetWriterError>> {
Box::pin(Self::write_meta(self, path))
}
fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::remove(self, path))
}
fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::remove_meta(self, path))
}
fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::rename(self, old_path, new_path))
}
fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::rename_meta(self, old_path, new_path))
}
fn remove_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::remove_directory(self, path))
}
fn remove_empty_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::remove_empty_directory(self, path))
}
fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::remove_assets_in_directory(self, path))
}
fn write_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::write_bytes(self, path, bytes))
}
fn write_meta_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::write_meta_bytes(self, path, bytes))
}
}
/// An "asset source change event" that occurs whenever asset (or asset metadata) is created/added/removed
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AssetSourceEvent {
/// An asset at this path was added.
AddedAsset(PathBuf),
/// An asset at this path was modified.
ModifiedAsset(PathBuf),
/// An asset at this path was removed.
RemovedAsset(PathBuf),
/// An asset at this path was renamed.
RenamedAsset { old: PathBuf, new: PathBuf },
/// Asset metadata at this path was added.
AddedMeta(PathBuf),
/// Asset metadata at this path was modified.
ModifiedMeta(PathBuf),
/// Asset metadata at this path was removed.
RemovedMeta(PathBuf),
/// Asset metadata at this path was renamed.
RenamedMeta { old: PathBuf, new: PathBuf },
/// A folder at the given path was added.
AddedFolder(PathBuf),
/// A folder at the given path was removed.
RemovedFolder(PathBuf),
/// A folder at the given path was renamed.
RenamedFolder { old: PathBuf, new: PathBuf },
/// Something of unknown type was removed. It is the job of the event handler to determine the type.
/// This exists because notify-rs produces "untyped" rename events without destination paths for unwatched folders, so we can't determine the type of
/// the rename.
RemovedUnknown {
/// 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.
path: PathBuf,
/// This field is only relevant if `path` is determined to be an asset path (and therefore not a folder). If this field is `true`,
/// then this event corresponds to a meta removal (not an asset removal) . If `false`, then this event corresponds to an asset removal
/// (not a meta removal).
is_meta: bool,
},
}
/// A handle to an "asset watcher" process, that will listen for and emit [`AssetSourceEvent`] values for as long as
/// [`AssetWatcher`] has not been dropped.
pub trait AssetWatcher: Send + Sync + 'static {}
/// An [`AsyncRead`] implementation capable of reading a [`Vec<u8>`].
pub struct VecReader {
bytes: Vec<u8>,
bytes_read: usize,
}
impl VecReader {
/// Create a new [`VecReader`] for `bytes`.
pub fn new(bytes: Vec<u8>) -> Self {
Self {
bytes_read: 0,
bytes,
}
}
}
impl AsyncRead for VecReader {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> Poll<futures_io::Result<usize>> {
if self.bytes_read >= self.bytes.len() {
Poll::Ready(Ok(0))
} else {
let n = ready!(Pin::new(&mut &self.bytes[self.bytes_read..]).poll_read(cx, buf))?;
self.bytes_read += n;
Poll::Ready(Ok(n))
}
}
}
impl AsyncSeek for VecReader {
fn poll_seek(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<std::io::Result<u64>> {
let result = match pos {
SeekFrom::Start(offset) => offset.try_into(),
SeekFrom::End(offset) => self.bytes.len().try_into().map(|len: i64| len - offset),
SeekFrom::Current(offset) => self
.bytes_read
.try_into()
.map(|bytes_read: i64| bytes_read + offset),
};
if let Ok(new_pos) = result {
if new_pos < 0 {
Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"seek position is out of range",
)))
} else {
self.bytes_read = new_pos as _;
Poll::Ready(Ok(new_pos as _))
}
} else {
Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"seek position is out of range",
)))
}
}
}
impl Reader for VecReader {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
StackFuture::from(async {
if self.bytes_read >= self.bytes.len() {
Ok(0)
} else {
buf.extend_from_slice(&self.bytes[self.bytes_read..]);
let n = self.bytes.len() - self.bytes_read;
self.bytes_read = self.bytes.len();
Ok(n)
}
})
}
}
/// An [`AsyncRead`] implementation capable of reading a [`&[u8]`].
pub struct SliceReader<'a> {
bytes: &'a [u8],
bytes_read: usize,
}
impl<'a> SliceReader<'a> {
/// Create a new [`SliceReader`] for `bytes`.
pub fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
bytes_read: 0,
}
}
}
impl<'a> AsyncRead for SliceReader<'a> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
if self.bytes_read >= self.bytes.len() {
Poll::Ready(Ok(0))
} else {
let n = ready!(Pin::new(&mut &self.bytes[self.bytes_read..]).poll_read(cx, buf))?;
self.bytes_read += n;
Poll::Ready(Ok(n))
}
}
}
impl<'a> AsyncSeek for SliceReader<'a> {
fn poll_seek(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<std::io::Result<u64>> {
let result = match pos {
SeekFrom::Start(offset) => offset.try_into(),
SeekFrom::End(offset) => self.bytes.len().try_into().map(|len: i64| len - offset),
SeekFrom::Current(offset) => self
.bytes_read
.try_into()
.map(|bytes_read: i64| bytes_read + offset),
};
if let Ok(new_pos) = result {
if new_pos < 0 {
Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"seek position is out of range",
)))
} else {
self.bytes_read = new_pos as _;
Poll::Ready(Ok(new_pos as _))
}
} else {
Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"seek position is out of range",
)))
}
}
}
impl Reader for SliceReader<'_> {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
StackFuture::from(async {
if self.bytes_read >= self.bytes.len() {
Ok(0)
} else {
buf.extend_from_slice(&self.bytes[self.bytes_read..]);
let n = self.bytes.len() - self.bytes_read;
self.bytes_read = self.bytes.len();
Ok(n)
}
})
}
}
/// Appends `.meta` to the given path.
pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
let mut meta_path = path.to_path_buf();
let mut extension = path.extension().unwrap_or_default().to_os_string();
extension.push(".meta");
meta_path.set_extension(extension);
meta_path
}
/// A [`PathBuf`] [`Stream`] implementation that immediately returns nothing.
struct EmptyPathStream;
impl Stream for EmptyPathStream {
type Item = PathBuf;
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
Poll::Ready(None)
}
}