Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions crates/bevy_asset/src/io/file/file_asset.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream,
Reader, ReaderRequiredFeatures, Writer,
Reader, ReaderNotSeekableError, SeekableReader, Writer,
};
use async_fs::{read_dir, File};
use futures_lite::StreamExt;
Expand All @@ -10,14 +10,14 @@ use std::path::Path;

use super::{FileAssetReader, FileAssetWriter};

impl Reader for File {}
impl Reader for File {
fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
Ok(self)
}
}

impl AssetReader for FileAssetReader {
async fn read<'a>(
&'a self,
path: &'a Path,
_required_features: ReaderRequiredFeatures,
) -> Result<impl Reader + 'a, AssetReaderError> {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let full_path = self.root_path.join(path);
File::open(&full_path).await.map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
Expand Down
12 changes: 6 additions & 6 deletions crates/bevy_asset/src/io/file/sync_file_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use futures_lite::Stream;

use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, AsyncSeek,
PathStream, Reader, ReaderRequiredFeatures, Writer,
PathStream, Reader, ReaderNotSeekableError, SeekableReader, Writer,
};

use alloc::{borrow::ToOwned, boxed::Box, vec::Vec};
Expand Down Expand Up @@ -48,6 +48,10 @@ impl Reader for FileReader {
{
stackfuture::StackFuture::from(async { self.0.read_to_end(buf) })
}

fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
Ok(self)
}
}

struct FileWriter(File);
Expand Down Expand Up @@ -95,11 +99,7 @@ impl Stream for DirReader {
}

impl AssetReader for FileAssetReader {
async fn read<'a>(
&'a self,
path: &'a Path,
_required_features: ReaderRequiredFeatures,
) -> Result<impl Reader + 'a, AssetReaderError> {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let full_path = self.root_path.join(path);
match File::open(&full_path) {
Ok(file) => Ok(FileReader(file)),
Expand Down
10 changes: 3 additions & 7 deletions crates/bevy_asset/src/io/gated.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader, ReaderRequiredFeatures};
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
use alloc::{boxed::Box, sync::Arc};
use async_channel::{Receiver, Sender};
use bevy_platform::{collections::HashMap, sync::RwLock};
Expand Down Expand Up @@ -55,11 +55,7 @@ impl<R: AssetReader> GatedReader<R> {
}

impl<R: AssetReader> AssetReader for GatedReader<R> {
async fn read<'a>(
&'a self,
path: &'a Path,
required_features: ReaderRequiredFeatures,
) -> Result<impl Reader + 'a, AssetReaderError> {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let receiver = {
let mut gates = self.gates.write().unwrap_or_else(PoisonError::into_inner);
let gates = gates
Expand All @@ -68,7 +64,7 @@ impl<R: AssetReader> AssetReader for GatedReader<R> {
gates.1.clone()
};
receiver.recv().await.unwrap();
let result = self.reader.read(path, required_features).await?;
let result = self.reader.read(path).await?;
Ok(result)
}

Expand Down
12 changes: 6 additions & 6 deletions crates/bevy_asset/src/io/memory.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::io::{
AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream, Reader,
ReaderRequiredFeatures,
ReaderNotSeekableError, SeekableReader,
};
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec, vec::Vec};
use bevy_platform::{
Expand Down Expand Up @@ -354,14 +354,14 @@ impl Reader for DataReader {
) -> stackfuture::StackFuture<'a, std::io::Result<usize>, { super::STACK_FUTURE_SIZE }> {
crate::io::read_to_end(self.data.value(), &mut self.bytes_read, buf)
}

fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
Ok(self)
}
}

impl AssetReader for MemoryAssetReader {
async fn read<'a>(
&'a self,
path: &'a Path,
_required_features: ReaderRequiredFeatures,
) -> Result<impl Reader + 'a, AssetReaderError> {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
self.root
.get_asset(path)
.map(|data| DataReader {
Expand Down
106 changes: 51 additions & 55 deletions crates/bevy_asset/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ use thiserror::Error;
/// Errors that occur while loading assets.
#[derive(Error, Debug, Clone)]
pub enum AssetReaderError {
#[error(
"A reader feature was required, but this AssetReader does not support that feature: {0}"
)]
UnsupportedFeature(#[from] UnsupportedReaderFeature),

/// Path not found.
#[error("Path not found: {}", _0.display())]
NotFound(PathBuf),
Expand All @@ -68,9 +63,6 @@ impl PartialEq for AssetReaderError {
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::UnsupportedFeature(feature), Self::UnsupportedFeature(other_feature)) => {
feature == other_feature
}
(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,
Expand All @@ -87,43 +79,6 @@ impl From<std::io::Error> for AssetReaderError {
}
}

/// An error for when a particular feature in [`ReaderRequiredFeatures`] is not supported.
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum UnsupportedReaderFeature {
/// The caller requested to be able to seek any way (forward, backward, from start/end), but
/// this is not supported by the [`AssetReader`].
#[error("the reader cannot seek in any direction")]
AnySeek,
}

/// The required features for a `Reader` that an `AssetLoader` may use.
///
/// This allows the asset loader to communicate with the asset source what features of the reader it
/// will use. This allows the asset source to return an error early (if a feature is unsupported),
/// or use a different reader implementation based on the required features to optimize reading
/// (e.g., using a simpler reader implementation if some features are not required).
///
/// These features **only** apply to the asset itself, and not any nested loads - those loaders will
/// request their own required features.
#[derive(Clone, Copy, Default)]
pub struct ReaderRequiredFeatures {
/// The kind of seek that the reader needs to support.
pub seek: SeekKind,
}

/// The kind of seeking that the reader supports.
#[derive(Clone, Copy, Default)]
pub enum SeekKind {
/// The reader can only seek forward.
///
/// Seeking forward is always required, since at the bare minimum, the reader could choose to
/// just read that many bytes and then drop them (effectively seeking forward).
#[default]
OnlyForward,
/// The reader can seek forward, backward, seek from the start, and seek from the end.
AnySeek,
}

/// 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),
Expand Down Expand Up @@ -154,7 +109,7 @@ pub use stackfuture::StackFuture;
/// [`SeekKind::AnySeek`] to indicate that they may seek backward, or from the start/end. A reader
/// implementation may choose to support that, or may just detect those kinds of seeks and return an
/// error.
pub trait Reader: AsyncRead + AsyncSeek + Unpin + Send + Sync {
pub trait Reader: AsyncRead + Unpin + Send + Sync {
/// Reads the entire contents of this reader and appends them to a vec.
///
/// # Note for implementors
Expand All @@ -168,15 +123,54 @@ pub trait Reader: AsyncRead + AsyncSeek + Unpin + Send + Sync {
let future = futures_lite::AsyncReadExt::read_to_end(self, buf);
StackFuture::from(future)
}

/// Casts this [`Reader`] as a [`SeekableReader`], which layers on [`AsyncSeek`] functionality.
/// Returns [`Ok`] if this [`Reader`] supports seeking. Otherwise returns [`Err`].
///
/// Implementers of [`Reader`] are highly encouraged to provide this functionality, as it makes the
/// reader compatible with "seeking" [`AssetLoader`](crate::AssetLoader) implementations.
///
/// [`AssetLoader`](crate::AssetLoader) implementations that call this are encouraged to provide fallback behavior
/// when it fails, such as reading into a seek-able [`Vec`] (or [`AsyncSeek`]-able [`VecReader`]):
///
/// ```
/// # use bevy_asset::io::{VecReader, Reader, AsyncSeekExt};
/// # use std::{io::SeekFrom, vec::Vec};
/// # let mut vec_reader = VecReader::new(Vec::new());
/// # let reader: &mut dyn Reader = &mut vec_reader;
/// let reader = match reader.seekable() {
/// Ok(seek) => seek,
/// Err(_) => {
/// reader.read_to_end(&mut data.bytes).await.unwrap();
/// &mut data
/// }
/// };
/// reader.seek(SeekFrom::Start(10)).await.unwrap();
/// ```
fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError>;
}

pub trait SeekableReader: Reader + AsyncSeek {}
Copy link
Member

@janhohenheim janhohenheim Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some documentation and breadcrumbs to other related traits? Nothing big.


impl<T: Reader + AsyncSeek> SeekableReader for T {}

#[derive(Error, Debug, Copy, Clone)]
#[error(
"The `Reader` returned by the current `AssetReader` does not support `AsyncSeek` behavior."
)]
pub struct ReaderNotSeekableError;
Comment on lines +146 to +150
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[derive(Error, Debug, Copy, Clone)]
#[error(
"The `Reader` returned by the current `AssetReader` does not support `AsyncSeek` behavior."
)]
pub struct ReaderNotSeekableError;
/// Error returned by [`Reader::seekable`] when the reader implementation does not support [`AsyncSeek`] behavior.
#[derive(Error, Debug, Copy, Clone)]
#[error(
"The `Reader` returned by the current `AssetReader` does not support `AsyncSeek` behavior."
)]
pub struct ReaderNotSeekableError;


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)
}

fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
(**self).seekable()
}
}

/// A future that returns a value or an [`AssetReaderError`]
Expand Down Expand Up @@ -245,11 +239,7 @@ pub trait AssetReader: Send + Sync + 'static {
/// # async fn read_meta_bytes<'a>(&'a self, path: &'a Path) -> Result<Vec<u8>, AssetReaderError> { unimplemented!() }
/// }
/// ```
fn read<'a>(
&'a self,
path: &'a Path,
required_features: ReaderRequiredFeatures,
) -> impl AssetReaderFuture<Value: Reader + 'a>;
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.
Expand Down Expand Up @@ -284,7 +274,6 @@ pub trait ErasedAssetReader: Send + Sync + 'static {
fn read<'a>(
&'a self,
path: &'a Path,
required_features: ReaderRequiredFeatures,
) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
/// Returns a future to load the full file data at the provided path.
fn read_meta<'a>(
Expand Down Expand Up @@ -313,10 +302,9 @@ impl<T: AssetReader> ErasedAssetReader for T {
fn read<'a>(
&'a self,
path: &'a Path,
required_features: ReaderRequiredFeatures,
) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
Box::pin(async move {
let reader = Self::read(self, path, required_features).await?;
let reader = Self::read(self, path).await?;
Ok(Box::new(reader) as Box<dyn Reader>)
})
}
Expand Down Expand Up @@ -640,7 +628,7 @@ pub trait AssetWatcher: Send + Sync + 'static {}

/// An [`AsyncRead`] implementation capable of reading a [`Vec<u8>`].
pub struct VecReader {
bytes: Vec<u8>,
pub bytes: Vec<u8>,
Copy link
Member

@janhohenheim janhohenheim Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind adding docs to this struct now that it's pub? How is a user meant to mutate this field? When? Is there anything to keep in mind, or can the user just freely mutate the Vec? Does read_bytes get affected by this?

bytes_read: usize,
}

Expand Down Expand Up @@ -685,6 +673,10 @@ impl Reader for VecReader {
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
read_to_end(&self.bytes, &mut self.bytes_read, buf)
}

fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
Ok(self)
}
}

/// An [`AsyncRead`] implementation capable of reading a [`&[u8]`].
Expand Down Expand Up @@ -730,6 +722,10 @@ impl Reader for SliceReader<'_> {
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
read_to_end(self.bytes, &mut self.bytes_read, buf)
}

fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
Ok(self)
}
}

/// Performs a read from the `slice` into `buf`.
Expand Down
29 changes: 10 additions & 19 deletions crates/bevy_asset/src/io/processor_gated.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
io::{
AssetReader, AssetReaderError, AssetSourceId, PathStream, Reader, ReaderRequiredFeatures,
AssetReader, AssetReaderError, AssetSourceId, PathStream, Reader, ReaderNotSeekableError,
SeekableReader,
},
processor::{ProcessStatus, ProcessingState},
AssetPath,
Expand All @@ -9,10 +10,10 @@ use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec::Vec};
use async_lock::RwLockReadGuardArc;
use core::{pin::Pin, task::Poll};
use futures_io::AsyncRead;
use std::{io::SeekFrom, path::Path};
use std::path::Path;
use tracing::trace;

use super::{AsyncSeek, ErasedAssetReader};
use super::ErasedAssetReader;

/// An [`AssetReader`] that will prevent asset (and asset metadata) read futures from returning for a
/// given path until that path has been processed by [`AssetProcessor`].
Expand Down Expand Up @@ -40,11 +41,7 @@ impl ProcessorGatedReader {
}

impl AssetReader for ProcessorGatedReader {
async fn read<'a>(
&'a self,
path: &'a Path,
required_features: ReaderRequiredFeatures,
) -> Result<impl Reader + 'a, AssetReaderError> {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone());
trace!("Waiting for processing to finish before reading {asset_path}");
let process_result = self
Expand All @@ -62,7 +59,7 @@ impl AssetReader for ProcessorGatedReader {
.processing_state
.get_transaction_lock(&asset_path)
.await?;
let asset_reader = self.reader.read(path, required_features).await?;
let asset_reader = self.reader.read(path).await?;
let reader = TransactionLockedReader::new(asset_reader, lock);
Ok(reader)
}
Expand Down Expand Up @@ -141,21 +138,15 @@ impl AsyncRead for TransactionLockedReader<'_> {
}
}

impl AsyncSeek for TransactionLockedReader<'_> {
fn poll_seek(
mut self: Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
pos: SeekFrom,
) -> Poll<std::io::Result<u64>> {
Pin::new(&mut self.reader).poll_seek(cx, pos)
}
}

impl Reader for TransactionLockedReader<'_> {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> stackfuture::StackFuture<'a, std::io::Result<usize>, { super::STACK_FUTURE_SIZE }> {
self.reader.read_to_end(buf)
}

fn seekable(&mut self) -> Result<&mut dyn SeekableReader, ReaderNotSeekableError> {
self.reader.seekable()
}
}
Loading
Loading