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::<AssetServer>` and `.resource_mut::<Assets<T>>`, 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.
This commit is contained in:
Nicola Papale 2024-02-27 01:28:26 +01:00 committed by GitHub
parent 5860e01432
commit f7f7e326e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 81 additions and 55 deletions

View File

@ -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<A: Asset>(&mut self, asset: impl Into<A>) -> Handle<A>;
/// Load an asset similarly to [`AssetServer::load`].
fn load_asset<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A>;
/// 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<AssetPath<'a>>,
settings: impl Fn(&mut S) + Send + Sync + 'static,
) -> Handle<A>;
}
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<A>) -> Handle<A> {
self.resource_mut::<Assets<A>>().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<AssetPath<'a>>) -> Handle<A> {
self.resource::<AssetServer>().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<AssetPath<'a>>,
settings: impl Fn(&mut S) + Send + Sync + 'static,
) -> Handle<A> {
self.resource::<AssetServer>()
.load_with_settings(path, settings)
}
}

View File

@ -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};

View File

@ -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::<AssetServer>();
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::<Image>("irradiance_volumes/Example.vxgi.ktx2");
let fox_animation =
asset_server.load::<AnimationClip>("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::<Image>("environment_maps/pisa_specular_rgb9e5_zstd.ktx2");
let mut mesh_assets = world.resource_mut::<Assets<Mesh>>();
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::<Assets<StandardMaterial>>();
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"),
}
}
}

View File

@ -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::<AssetServer>();
// 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,
}

View File

@ -131,9 +131,7 @@ impl FromWorld for GameOfLifePipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let texture_bind_group_layout = GameOfLifeImage::bind_group_layout(render_device);
let shader = world
.resource::<AssetServer>()
.load("shaders/game_of_life.wgsl");
let shader = world.load_asset("shaders/game_of_life.wgsl");
let pipeline_cache = world.resource::<PipelineCache>();
let init_pipeline = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
label: None,

View File

@ -248,9 +248,7 @@ impl FromWorld for PostProcessPipeline {
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
// Get the shader handle
let shader = world
.resource::<AssetServer>()
.load("shaders/post_processing.wgsl");
let shader = world.load_asset("shaders/post_processing.wgsl");
let pipeline_id = world
.resource_mut::<PipelineCache>()

View File

@ -182,13 +182,10 @@ struct CustomPipeline {
impl FromWorld for CustomPipeline {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
let shader = asset_server.load("shaders/instancing.wgsl");
let mesh_pipeline = world.resource::<MeshPipeline>();
CustomPipeline {
shader,
shader: world.load_asset("shaders/instancing.wgsl"),
mesh_pipeline: mesh_pipeline.clone(),
}
}

View File

@ -52,10 +52,9 @@ struct ButtonMaterials {
}
impl FromWorld for ButtonMaterials {
fn from_world(world: &mut World) -> Self {
let mut materials = world.resource_mut::<Assets<ColorMaterial>>();
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::<Assets<Mesh>>();
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(),
}
}
}