Files
bevy/examples/asset/custom_asset_reader.rs
T
Carter Anderson cd977df8eb bevy_asset: support upgrading Reader to SeekableReader (#22182)
# Objective

#22104 added `AsyncSeek` support, but it has some downsides:

- It is very "loose": it relies on `AssetLoader` to "attest" that it
needs the seek feature. However, AssetLoaders both have access to the
`AsyncSeek` API no matter what (it is a supertrait of Reader), and
AssetReaders provide `AsyncSeek` behavior even if it isn't requested. In
practice this is likely to create situations where AssetLoaders don't
request `AsyncSeek` and just rely on it being provided by default,
causing unexpected incompatibilities when AssetLoader consumers try to
use other AssetReaders that don't support AsyncSeek.
- It encourages building "fallback" behavior into the AssetReader in
cases where AsyncSeek cannot be supported directly (ex: read the while
contents into a Vec). From the perspective of loaders, this _silently_
changes the performance characteristics, and _forces_ a specific kind of
fallback behavior. It would be better if the fallback behavior was in
the hands of the AssetLoader.

## Solution

- Remove `ReaderRequiredFeatures` and associated functionality 
- Add a new `SeekableReader` trait, where `SeekableReader: Reader +
AsyncSeek`.
- Add a new `Reader::seekable(&mut self) -> Result<&mut dyn
SeekableReader, ReaderNotSeekableError>`, which can either fail or cast
to `SeekableReader`, if that is supported.

```rust
let seekable_reader = reader.seekable()?;
seekable_reader.seek(SeekFrom::Start(10)).await?;
```

This gives `AssetLoader` implementers more clarity when it comes to
`Reader` feature support, gives them more autonomy over fallback
behavior, makes our APIs more static, and cuts down on the complexity of
the system as a whole.

---------

Co-authored-by: andriyDev <andriydzikh@gmail.com>
Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
2026-01-07 21:43:49 +00:00

66 lines
2.1 KiB
Rust

//! Implements a custom asset io loader.
//! An [`AssetReader`] is what the asset server uses to read the raw bytes of assets.
//! It does not know anything about the asset formats, only how to talk to the underlying storage.
use bevy::{
asset::io::{
AssetReader, AssetReaderError, AssetSource, AssetSourceBuilder, AssetSourceId,
ErasedAssetReader, PathStream, Reader,
},
prelude::*,
};
use std::path::Path;
/// A custom asset reader implementation that wraps a given asset reader implementation
struct CustomAssetReader(Box<dyn ErasedAssetReader>);
impl AssetReader for CustomAssetReader {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
info!("Reading {}", path.display());
self.0.read(path).await
}
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
self.0.read_meta(path).await
}
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<PathStream>, AssetReaderError> {
self.0.read_directory(path).await
}
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
self.0.is_directory(path).await
}
}
/// A plugins that registers our new asset reader
struct CustomAssetReaderPlugin;
impl Plugin for CustomAssetReaderPlugin {
fn build(&self, app: &mut App) {
app.register_asset_source(
AssetSourceId::Default,
AssetSourceBuilder::new(|| {
Box::new(CustomAssetReader(
// This is the default reader for the current platform
AssetSource::get_default_reader("assets".to_string())(),
))
}),
);
}
}
fn main() {
App::new()
.add_plugins((CustomAssetReaderPlugin, DefaultPlugins))
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
commands.spawn(Sprite::from_image(asset_server.load("branding/icon.png")));
}