Skip to content

Commit

Permalink
Add methods to directly load assets from World (#12023)
Browse files Browse the repository at this point in the history
# 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.
  • Loading branch information
nicopap authored Feb 27, 2024
1 parent 5860e01 commit f7f7e32
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 55 deletions.
50 changes: 50 additions & 0 deletions crates/bevy_asset/src/direct_access_ext.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
6 changes: 4 additions & 2 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down
41 changes: 12 additions & 29 deletions examples/3d/irradiance_volumes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}
}
}
Expand Down
10 changes: 4 additions & 6 deletions examples/3d/reflection_probes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
4 changes: 1 addition & 3 deletions examples/shader/compute_shader_game_of_life.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 1 addition & 3 deletions examples/shader/post_processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>()
Expand Down
5 changes: 1 addition & 4 deletions examples/shader/shader_instancing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Expand Down
16 changes: 8 additions & 8 deletions examples/tools/gamepad_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
}
Expand All @@ -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(),
}
}
}
Expand Down

0 comments on commit f7f7e32

Please sign in to comment.