From 596bed8ce2e863cc031011910e8a2a270d51b142 Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Fri, 18 Dec 2020 11:34:44 -0800 Subject: [PATCH] add ability to provide custom a `AssetIo` implementation (#1037) make it easier to override the default asset IO instance --- Cargo.toml | 4 + crates/bevy_asset/src/asset_server.rs | 6 +- crates/bevy_asset/src/lib.rs | 54 +++++++------ examples/asset/custom_asset_io.rs | 104 ++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 22 deletions(-) create mode 100644 examples/asset/custom_asset_io.rs diff --git a/Cargo.toml b/Cargo.toml index 3280002be3532..4f8cdf8038337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,6 +177,10 @@ path = "examples/asset/asset_loading.rs" name = "custom_asset" path = "examples/asset/custom_asset.rs" +[[example]] +name = "custom_asset_io" +path = "examples/asset/custom_asset_io.rs" + [[example]] name = "audio" path = "examples/audio/audio.rs" diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 70fa26eb4164a..fafff56008346 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -60,6 +60,10 @@ impl Clone for AssetServer { impl AssetServer { pub fn new(source_io: T, task_pool: TaskPool) -> Self { + Self::with_boxed_io(Box::new(source_io), task_pool) + } + + pub fn with_boxed_io(asset_io: Box, task_pool: TaskPool) -> Self { AssetServer { server: Arc::new(AssetServerInternal { loaders: Default::default(), @@ -69,7 +73,7 @@ impl AssetServer { handle_to_path: Default::default(), asset_lifecycles: Default::default(), task_pool, - asset_io: Box::new(source_io), + asset_io, }), } } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 1e77e14443626..5c9b4133b9d81 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -51,28 +51,41 @@ impl Default for AssetServerSettings { } } +/// Create an instance of the platform default `AssetIo` +/// +/// This is useful when providing a custom `AssetIo` instance that needs to +/// delegate to the default `AssetIo` for the platform. +pub fn create_platform_default_asset_io(app: &mut AppBuilder) -> Box { + let settings = app + .resources_mut() + .get_or_insert_with(AssetServerSettings::default); + + #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] + let source = FileAssetIo::new(&settings.asset_folder); + #[cfg(target_arch = "wasm32")] + let source = WasmAssetIo::new(&settings.asset_folder); + #[cfg(target_os = "android")] + let source = AndroidAssetIo::new(&settings.asset_folder); + + Box::new(source) +} + impl Plugin for AssetPlugin { fn build(&self, app: &mut AppBuilder) { - let task_pool = app - .resources() - .get::() - .expect("`IoTaskPool` resource not found.") - .0 - .clone(); - - let asset_server = { - let settings = app - .resources_mut() - .get_or_insert_with(AssetServerSettings::default); - - #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] - let source = FileAssetIo::new(&settings.asset_folder); - #[cfg(target_arch = "wasm32")] - let source = WasmAssetIo::new(&settings.asset_folder); - #[cfg(target_os = "android")] - let source = AndroidAssetIo::new(&settings.asset_folder); - AssetServer::new(source, task_pool) - }; + if app.resources().get::().is_none() { + let task_pool = app + .resources() + .get::() + .expect("`IoTaskPool` resource not found.") + .0 + .clone(); + + let source = create_platform_default_asset_io(app); + + let asset_server = AssetServer::with_boxed_io(source, task_pool); + + app.add_resource(asset_server); + } app.add_stage_before( bevy_app::stage::PRE_UPDATE, @@ -84,7 +97,6 @@ impl Plugin for AssetPlugin { stage::ASSET_EVENTS, SystemStage::parallel(), ) - .add_resource(asset_server) .register_type::() .add_system_to_stage( bevy_app::stage::PRE_UPDATE, diff --git a/examples/asset/custom_asset_io.rs b/examples/asset/custom_asset_io.rs new file mode 100644 index 0000000000000..97058a36b5b4b --- /dev/null +++ b/examples/asset/custom_asset_io.rs @@ -0,0 +1,104 @@ +use bevy::{ + asset::{AssetIo, AssetIoError}, + prelude::*, + utils::BoxedFuture, +}; +use std::path::{Path, PathBuf}; + +/// A custom asset io implementation that simply defers to the platform default +/// implementation. +/// +/// This can be used as a starting point for developing a useful implementation +/// that can defer to the default when needed. +struct CustomAssetIo(Box); + +impl AssetIo for CustomAssetIo { + fn load_path<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result, AssetIoError>> { + println!("load_path({:?})", path); + self.0.load_path(path) + } + + fn read_directory( + &self, + path: &Path, + ) -> Result>, AssetIoError> { + println!("read_directory({:?})", path); + self.0.read_directory(path) + } + + fn is_directory(&self, path: &Path) -> bool { + println!("is_directory({:?})", path); + self.0.is_directory(path) + } + + fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError> { + println!("watch_path_for_changes({:?})", path); + self.0.watch_path_for_changes(path) + } + + fn watch_for_changes(&self) -> Result<(), AssetIoError> { + println!("watch_for_changes()"); + self.0.watch_for_changes() + } +} + +/// A plugin used to execute the override of the asset io +struct CustomAssetIoPlugin; + +impl Plugin for CustomAssetIoPlugin { + fn build(&self, app: &mut AppBuilder) { + // must get a hold of the task pool in order to create the asset server + + let task_pool = app + .resources() + .get::() + .expect("`IoTaskPool` resource not found.") + .0 + .clone(); + + let asset_io = { + // the platform default asset io requires a reference to the app + // builder to find its configuration + + let default_io = bevy::asset::create_platform_default_asset_io(app); + + // create the custom asset io instance + + CustomAssetIo(default_io) + }; + + // the asset server is constructed and added the resource manager + + app.add_resource(AssetServer::new(asset_io, task_pool)); + } +} + +fn main() { + App::build() + .add_plugins_with(DefaultPlugins, |group| { + // the custom asset io plugin must be inserted in-between the + // `CorePlugin' and `AssetPlugin`. It needs to be after the + // CorePlugin, so that the IO task pool has already been constructed. + // And it must be before the `AssetPlugin` so that the asset plugin + // doesn't create another instance of an assert server. In general, + // the AssetPlugin should still run so that other aspects of the + // asset system are initialized correctly. + group.add_before::(CustomAssetIoPlugin) + }) + .add_startup_system(setup) + .run(); +} + +fn setup( + commands: &mut Commands, + asset_server: Res, + mut materials: ResMut>, +) { + let texture_handle = asset_server.load("branding/icon.png"); + commands + .spawn(Camera2dBundle::default()) + .spawn(SpriteBundle { + material: materials.add(texture_handle.into()), + ..Default::default() + }); +}