mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 14:40:19 +00:00
5876352206
# 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 {} ```
71 lines
2 KiB
TOML
71 lines
2 KiB
TOML
[package]
|
|
name = "bevy_asset"
|
|
version = "0.14.0-dev"
|
|
edition = "2021"
|
|
description = "Provides asset functionality for Bevy Engine"
|
|
homepage = "https://bevyengine.org"
|
|
repository = "https://github.com/bevyengine/bevy"
|
|
license = "MIT OR Apache-2.0"
|
|
keywords = ["bevy"]
|
|
|
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
|
|
[features]
|
|
file_watcher = ["notify-debouncer-full", "watch"]
|
|
embedded_watcher = ["file_watcher"]
|
|
multi_threaded = ["bevy_tasks/multi_threaded"]
|
|
asset_processor = []
|
|
watch = []
|
|
trace = []
|
|
|
|
[dependencies]
|
|
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
|
|
bevy_asset_macros = { path = "macros", version = "0.14.0-dev" }
|
|
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
|
|
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
|
|
"uuid",
|
|
] }
|
|
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
|
|
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
|
|
|
|
stackfuture = "0.3"
|
|
async-broadcast = "0.5"
|
|
async-fs = "2.0"
|
|
async-lock = "3.0"
|
|
crossbeam-channel = "0.5"
|
|
downcast-rs = "1.2"
|
|
futures-io = "0.3"
|
|
futures-lite = "2.0.1"
|
|
blake3 = "1.5"
|
|
parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] }
|
|
ron = "0.8"
|
|
serde = { version = "1", features = ["derive"] }
|
|
thiserror = "1.0"
|
|
uuid = { version = "1.0", features = ["v4"] }
|
|
|
|
[target.'cfg(target_os = "android")'.dependencies]
|
|
bevy_winit = { path = "../bevy_winit", version = "0.14.0-dev" }
|
|
|
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
wasm-bindgen = { version = "0.2" }
|
|
web-sys = { version = "0.3", features = [
|
|
"Window",
|
|
"Response",
|
|
"WorkerGlobalScope",
|
|
] }
|
|
wasm-bindgen-futures = "0.4"
|
|
js-sys = "0.3"
|
|
|
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
|
notify-debouncer-full = { version = "0.3.1", optional = true }
|
|
|
|
[dev-dependencies]
|
|
bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
|
|
bevy_log = { path = "../bevy_log", version = "0.14.0-dev" }
|
|
|
|
[lints]
|
|
workspace = true
|
|
|
|
[package.metadata.docs.rs]
|
|
rustdoc-args = ["-Zunstable-options", "--cfg", "docsrs"]
|
|
all-features = true
|