From f7f7e326e5fd7eb688fb605ff45f1d0502194ac0 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Tue, 27 Feb 2024 01:28:26 +0100 Subject: [PATCH] Add methods to directly load assets from World (#12023) # Objective `FromWorld` is often used to group loading and creation of assets for resources. With this setup, users often end up repetitively calling `.resource::` and `.resource_mut::>`, and may have difficulties handling lifetimes of the returned references. ## Solution Add extension methods to `World` to add and load assets, through a new extension trait defined in `bevy_asset`. ### Other considerations * This might be a bit too "magic", as it makes implicit the resource access. * We could also implement `DirectAssetAccessExt` on `App`, but it didn't feel necessary, as `FromWorld` is the principal use-case here. --- ## Changelog * Add the `DirectAssetAccessExt` trait, which adds the `add_asset`, `load_asset` and `load_asset_with_settings` method to the `World` type. --- crates/bevy_asset/src/direct_access_ext.rs | 50 +++++++++++++++++++ crates/bevy_asset/src/lib.rs | 6 ++- examples/3d/irradiance_volumes.rs | 41 +++++---------- examples/3d/reflection_probes.rs | 10 ++-- .../shader/compute_shader_game_of_life.rs | 4 +- examples/shader/post_processing.rs | 4 +- examples/shader/shader_instancing.rs | 5 +- examples/tools/gamepad_viewer.rs | 16 +++--- 8 files changed, 81 insertions(+), 55 deletions(-) create mode 100644 crates/bevy_asset/src/direct_access_ext.rs diff --git a/crates/bevy_asset/src/direct_access_ext.rs b/crates/bevy_asset/src/direct_access_ext.rs new file mode 100644 index 0000000000000..bfa7fa17b29c0 --- /dev/null +++ b/crates/bevy_asset/src/direct_access_ext.rs @@ -0,0 +1,50 @@ +//! Add methods on `World` to simplify loading assets when all +//! you have is a `World`. + +use bevy_ecs::world::World; + +use crate::{meta::Settings, Asset, AssetPath, AssetServer, Assets, Handle}; + +pub trait DirectAssetAccessExt { + /// Insert an asset similarly to [`Assets::add`]. + fn add_asset(&mut self, asset: impl Into) -> Handle; + + /// Load an asset similarly to [`AssetServer::load`]. + fn load_asset<'a, A: Asset>(&self, path: impl Into>) -> Handle; + + /// Load an asset with settings, similarly to [`AssetServer::load_with_settings`]. + fn load_asset_with_settings<'a, A: Asset, S: Settings>( + &self, + path: impl Into>, + settings: impl Fn(&mut S) + Send + Sync + 'static, + ) -> Handle; +} +impl DirectAssetAccessExt for World { + /// Insert an asset similarly to [`Assets::add`]. + /// + /// # Panics + /// If `self` doesn't have an [`AssetServer`] resource initialized yet. + fn add_asset<'a, A: Asset>(&mut self, asset: impl Into) -> Handle { + self.resource_mut::>().add(asset) + } + + /// Load an asset similarly to [`AssetServer::load`]. + /// + /// # Panics + /// If `self` doesn't have an [`AssetServer`] resource initialized yet. + fn load_asset<'a, A: Asset>(&self, path: impl Into>) -> Handle { + self.resource::().load(path) + } + /// Load an asset with settings, similarly to [`AssetServer::load_with_settings`]. + /// + /// # Panics + /// If `self` doesn't have an [`AssetServer`] resource initialized yet. + fn load_asset_with_settings<'a, A: Asset, S: Settings>( + &self, + path: impl Into>, + settings: impl Fn(&mut S) + Send + Sync + 'static, + ) -> Handle { + self.resource::() + .load_with_settings(path, settings) + } +} diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index a0b38af09f87b..9fa056e3bced7 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -10,12 +10,13 @@ pub mod transformer; pub mod prelude { #[doc(hidden)] pub use crate::{ - Asset, AssetApp, AssetEvent, AssetId, AssetMode, AssetPlugin, AssetServer, Assets, Handle, - UntypedHandle, + Asset, AssetApp, AssetEvent, AssetId, AssetMode, AssetPlugin, AssetServer, Assets, + DirectAssetAccessExt, Handle, UntypedHandle, }; } mod assets; +mod direct_access_ext; mod event; mod folder; mod handle; @@ -27,6 +28,7 @@ mod server; pub use assets::*; pub use bevy_asset_macros::Asset; +pub use direct_access_ext::DirectAssetAccessExt; pub use event::*; pub use folder::*; pub use futures_lite::{AsyncReadExt, AsyncWriteExt}; diff --git a/examples/3d/irradiance_volumes.rs b/examples/3d/irradiance_volumes.rs index 3d34dcecc6721..34f0608fcebd5 100644 --- a/examples/3d/irradiance_volumes.rs +++ b/examples/3d/irradiance_volumes.rs @@ -512,36 +512,19 @@ fn handle_mouse_clicks( impl FromWorld for ExampleAssets { fn from_world(world: &mut World) -> Self { - // Load all the assets. - let asset_server = world.resource::(); - let fox = asset_server.load("models/animated/Fox.glb#Scene0"); - let main_scene = - asset_server.load("models/IrradianceVolumeExample/IrradianceVolumeExample.glb#Scene0"); - let irradiance_volume = asset_server.load::("irradiance_volumes/Example.vxgi.ktx2"); - let fox_animation = - asset_server.load::("models/animated/Fox.glb#Animation1"); - - // Just use a specular map for the skybox since it's not too blurry. - // In reality you wouldn't do this--you'd use a real skybox texture--but - // reusing the textures like this saves space in the Bevy repository. - let skybox = asset_server.load::("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"); - - let mut mesh_assets = world.resource_mut::>(); - let main_sphere = mesh_assets.add(Sphere::default().mesh().uv(32, 18)); - let voxel_cube = mesh_assets.add(Cuboid::default()); - - let mut standard_material_assets = world.resource_mut::>(); - let main_material = standard_material_assets.add(LegacyColor::SILVER); - ExampleAssets { - main_sphere, - fox, - main_sphere_material: main_material, - main_scene, - irradiance_volume, - fox_animation, - voxel_cube, - skybox, + main_sphere: world.add_asset(Sphere::default().mesh().uv(32, 18)), + fox: world.load_asset("models/animated/Fox.glb#Scene0"), + main_sphere_material: world.add_asset(LegacyColor::SILVER), + main_scene: world + .load_asset("models/IrradianceVolumeExample/IrradianceVolumeExample.glb#Scene0"), + irradiance_volume: world.load_asset("irradiance_volumes/Example.vxgi.ktx2"), + fox_animation: world.load_asset("models/animated/Fox.glb#Animation1"), + voxel_cube: world.add_asset(Cuboid::default()), + // Just use a specular map for the skybox since it's not too blurry. + // In reality you wouldn't do this--you'd use a real skybox texture--but + // reusing the textures like this saves space in the Bevy repository. + skybox: world.load_asset("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), } } } diff --git a/examples/3d/reflection_probes.rs b/examples/3d/reflection_probes.rs index aa0bd348d58ad..92c5207278c8c 100644 --- a/examples/3d/reflection_probes.rs +++ b/examples/3d/reflection_probes.rs @@ -331,17 +331,15 @@ fn rotate_camera( // Loads the cubemaps from the assets directory. impl FromWorld for Cubemaps { fn from_world(world: &mut World) -> Self { - let asset_server = world.resource::(); - // Just use the specular map for the skybox since it's not too blurry. // In reality you wouldn't do this--you'd use a real skybox texture--but // reusing the textures like this saves space in the Bevy repository. - let specular_map = asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"); + let specular_map = world.load_asset("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"); Cubemaps { - diffuse: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), - specular_reflection_probe: asset_server - .load("environment_maps/cubes_reflection_probe_specular_rgb9e5_zstd.ktx2"), + diffuse: world.load_asset("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), + specular_reflection_probe: world + .load_asset("environment_maps/cubes_reflection_probe_specular_rgb9e5_zstd.ktx2"), specular_environment_map: specular_map.clone(), skybox: specular_map, } diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index ad09f87b6c051..ebd00c3894d22 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -131,9 +131,7 @@ impl FromWorld for GameOfLifePipeline { fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); let texture_bind_group_layout = GameOfLifeImage::bind_group_layout(render_device); - let shader = world - .resource::() - .load("shaders/game_of_life.wgsl"); + let shader = world.load_asset("shaders/game_of_life.wgsl"); let pipeline_cache = world.resource::(); let init_pipeline = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { label: None, diff --git a/examples/shader/post_processing.rs b/examples/shader/post_processing.rs index f666e2efa930b..3076f60ea9d02 100644 --- a/examples/shader/post_processing.rs +++ b/examples/shader/post_processing.rs @@ -248,9 +248,7 @@ impl FromWorld for PostProcessPipeline { let sampler = render_device.create_sampler(&SamplerDescriptor::default()); // Get the shader handle - let shader = world - .resource::() - .load("shaders/post_processing.wgsl"); + let shader = world.load_asset("shaders/post_processing.wgsl"); let pipeline_id = world .resource_mut::() diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index ef637882d9021..347c4864f36a0 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -182,13 +182,10 @@ struct CustomPipeline { impl FromWorld for CustomPipeline { fn from_world(world: &mut World) -> Self { - let asset_server = world.resource::(); - let shader = asset_server.load("shaders/instancing.wgsl"); - let mesh_pipeline = world.resource::(); CustomPipeline { - shader, + shader: world.load_asset("shaders/instancing.wgsl"), mesh_pipeline: mesh_pipeline.clone(), } } diff --git a/examples/tools/gamepad_viewer.rs b/examples/tools/gamepad_viewer.rs index 984b553d8e5e8..7335dd89e016d 100644 --- a/examples/tools/gamepad_viewer.rs +++ b/examples/tools/gamepad_viewer.rs @@ -52,10 +52,9 @@ struct ButtonMaterials { } impl FromWorld for ButtonMaterials { fn from_world(world: &mut World) -> Self { - let mut materials = world.resource_mut::>(); Self { - normal: materials.add(NORMAL_BUTTON_COLOR), - active: materials.add(ACTIVE_BUTTON_COLOR), + normal: world.add_asset(NORMAL_BUTTON_COLOR), + active: world.add_asset(ACTIVE_BUTTON_COLOR), } } } @@ -68,12 +67,13 @@ struct ButtonMeshes { } impl FromWorld for ButtonMeshes { fn from_world(world: &mut World) -> Self { - let mut meshes = world.resource_mut::>(); Self { - circle: meshes.add(Circle::new(BUTTON_RADIUS)).into(), - triangle: meshes.add(RegularPolygon::new(BUTTON_RADIUS, 3)).into(), - start_pause: meshes.add(Rectangle::from_size(START_SIZE)).into(), - trigger: meshes.add(Rectangle::from_size(TRIGGER_SIZE)).into(), + circle: world.add_asset(Circle::new(BUTTON_RADIUS)).into(), + triangle: world + .add_asset(RegularPolygon::new(BUTTON_RADIUS, 3)) + .into(), + start_pause: world.add_asset(Rectangle::from_size(START_SIZE)).into(), + trigger: world.add_asset(Rectangle::from_size(TRIGGER_SIZE)).into(), } } }