Skip to content
Merged
2 changes: 1 addition & 1 deletion crates/bevy_asset/src/asset_changed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ mod tests {
.iter()
.find_map(|(h, a)| (a.0 == i).then_some(h))
.unwrap();
let asset = assets.get_mut(id).unwrap();
let mut asset = assets.get_mut(id).unwrap();
println!("setting new value for {}", asset.0);
asset.1 = "new_value";
};
Expand Down
72 changes: 66 additions & 6 deletions crates/bevy_asset/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use bevy_ecs::{
use bevy_platform::collections::HashMap;
use bevy_reflect::{Reflect, TypePath};
use core::{any::TypeId, iter::Enumerate, marker::PhantomData, sync::atomic::AtomicU32};
use std::ops::{Deref, DerefMut};
use crossbeam_channel::{Receiver, Sender};
use serde::{Deserialize, Serialize};
use thiserror::Error;
Expand Down Expand Up @@ -348,7 +349,7 @@ impl<A: Asset> Assets<A> {
&mut self,
id: impl Into<AssetId<A>>,
insert_fn: impl FnOnce() -> A,
) -> Result<&mut A, InvalidGenerationError> {
) -> Result<AssetMut<'_, A>, InvalidGenerationError> {
let id: AssetId<A> = id.into();
if self.get(id).is_none() {
self.insert(id, insert_fn())?;
Expand Down Expand Up @@ -436,16 +437,20 @@ impl<A: Asset> Assets<A> {
/// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
#[inline]
pub fn get_mut(&mut self, id: impl Into<AssetId<A>>) -> Option<&mut A> {
pub fn get_mut(&mut self, id: impl Into<AssetId<A>>) -> Option<AssetMut<'_, A>> {
let id: AssetId<A> = id.into();
let result = match id {
AssetId::Index { index, .. } => self.dense_storage.get_mut(index),
AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid),
};
if result.is_some() {
self.queued_events.push(AssetEvent::Modified { id });
}
result
Some(AssetMut {
asset: result?,
guard: AssetMutChangeNotifier {
changed: false,
asset_id: id,
queued_events: &mut self.queued_events,
},
})
}

/// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
Expand Down Expand Up @@ -618,6 +623,61 @@ impl<A: Asset> Assets<A> {
}
}

/// Unique mutable borrow of an asset.
///
/// [AssetEvent::Modified] events will be only triggered if an asset itself is mutably borrowed.
///
/// Just as an example, this allows checking if a material property has changed
/// before modifying it to avoid unnecessary material extraction down the pipeline.
pub struct AssetMut<'a, A: Asset> {
asset: &'a mut A,
guard: AssetMutChangeNotifier<'a, A>,
}

impl<'a, A: Asset> AssetMut<'a, A> {
/// Marks with inner asset as modified and returns reference to it.
pub fn into_inner(mut self) -> &'a mut A {
self.guard.changed = true;
self.asset
}

/// Returns reference to the inner asset but doesn't mark it as modified.
pub fn into_inner_untracked(self) -> &'a mut A {
self.asset
}
}

impl<'a, A: Asset> Deref for AssetMut<'a, A> {
type Target = A;

fn deref(&self) -> &Self::Target {
self.asset
}
}

impl<'a, A: Asset> DerefMut for AssetMut<'a, A> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.guard.changed = true;
self.asset
}
}

/// Helper struct to allow safe destructuring of the [AssetMut::into_inner]
/// while also keeping strong change tracking guarantees.
struct AssetMutChangeNotifier<'a, A: Asset> {
changed: bool,
asset_id: AssetId<A>,
queued_events: &'a mut Vec<AssetEvent<A>>,
}

impl<'a, A: Asset> Drop for AssetMutChangeNotifier<'a, A> {
fn drop(&mut self) {
if self.changed {
self.queued_events.push(AssetEvent::Modified { id: self.asset_id });
}
}
}

/// A mutable iterator over [`Assets`].
pub struct AssetsMutIterator<'a, A: Asset> {
queued_events: &'a mut Vec<AssetEvent<A>>,
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1229,7 +1229,7 @@ mod tests {

{
let mut texts = app.world_mut().resource_mut::<Assets<CoolText>>();
let a = texts.get_mut(a_id).unwrap();
let mut a = texts.get_mut(a_id).unwrap();
a.text = "Changed".to_string();
}

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_asset/src/reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
#[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")]
let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
let asset = assets.get_mut(asset_id.typed_debug_checked());
asset.map(|asset| asset as &mut dyn Reflect)
asset.map(|asset| asset.into_inner() as &mut dyn Reflect)
},
add: |world, value| {
let mut assets = world.resource_mut::<Assets<A>>();
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_dev_tools/src/frame_time_graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ fn update_frame_time_values(
.map(|x| *x as f32 / 1000.0)
.collect::<Vec<_>>();
for (_, material) in frame_time_graph_materials.iter_mut() {
let buffer = buffers.get_mut(&material.values).unwrap();
let mut buffer = buffers.get_mut(&material.values).unwrap();

buffer.set_data(frame_times.clone());
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_feathers/src/controls/color_plane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ fn update_plane_color(

if let Ok(material_node) = q_material_node.get(*inner_ent) {
// Node component exists, update it
if let Some(material) = r_materials.get_mut(material_node.id()) {
if let Some(mut material) = r_materials.get_mut(material_node.id()) {
// Update properties
material.plane = *plane;
material.fixed_channel = plane_value.0.z;
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_gizmos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ fn update_gizmo_meshes<Config: GizmoConfigGroup>(
handles.handles.insert(TypeId::of::<Config>(), None);
} else if let Some(handle) = handles.handles.get_mut(&TypeId::of::<Config>()) {
if let Some(handle) = handle {
let gizmo = gizmo_assets.get_mut(handle.id()).unwrap();
let mut gizmo = gizmo_assets.get_mut(handle.id()).unwrap();

gizmo.buffer.list_positions = mem::take(&mut storage.list_positions);
gizmo.buffer.list_colors = mem::take(&mut storage.list_colors);
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/wireframe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ fn global_color_changed(
mut materials: ResMut<Assets<WireframeMaterial>>,
global_material: Res<GlobalWireframeMaterial>,
) {
if let Some(global_material) = materials.get_mut(&global_material.handle) {
if let Some(mut global_material) = materials.get_mut(&global_material.handle) {
global_material.color = config.default_color;
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ fn global_color_changed(
mut materials: ResMut<Assets<Wireframe2dMaterial>>,
global_material: Res<GlobalWireframeMaterial>,
) {
if let Some(global_material) = materials.get_mut(&global_material.handle) {
if let Some(mut global_material) = materials.get_mut(&global_material.handle) {
global_material.color = config.default_color;
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_sprite_render/src/tilemap_chunk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ pub fn update_tilemap_chunk_indices(
);
continue;
};
let Some(tile_data_image) = images.get_mut(&material.tile_data) else {
let Some(mut tile_data_image) = images.get_mut(&material.tile_data) else {
warn!(
"TilemapChunkMaterial tile data image not found for tilemap chunk {}",
chunk_entity
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_text/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub fn load_font_assets_into_fontdb_system(
let font_system = &mut cosmic_font_system.0;
for event in events.read() {
if let AssetEvent::Added { id } = event
&& let Some(font) = fonts.get_mut(*id)
&& let Some(mut font) = fonts.get_mut(*id)
{
let data = Arc::clone(&font.data);
font.ids = font_system
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_text/src/font_atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,16 @@ impl FontAtlas {
texture: &Image,
offset: IVec2,
) -> Result<(), TextError> {
let atlas_layout = atlas_layouts
let mut atlas_layout = atlas_layouts
.get_mut(&self.texture_atlas)
.ok_or(TextError::MissingAtlasLayout)?;
let atlas_texture = textures
let mut atlas_texture = textures
.get_mut(&self.texture)
.ok_or(TextError::MissingAtlasTexture)?;

if let Ok(glyph_index) =
self.dynamic_texture_atlas_builder
.add_texture(atlas_layout, texture, atlas_texture)
.add_texture(&mut atlas_layout, texture, &mut atlas_texture)
{
self.glyph_to_atlas_index.insert(
cache_key,
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/cpu_draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ fn draw(
}

// Get the image from Bevy's asset storage.
let image = images.get_mut(&my_handle.0).expect("Image not found");
let mut image = images.get_mut(&my_handle.0).expect("Image not found");

// Compute the position of the pixel to draw.

Expand Down
2 changes: 1 addition & 1 deletion examples/2d/texture_atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ fn create_texture_atlas(
let texture = textures.add(texture);

// Update the sampling settings of the texture atlas
let image = textures.get_mut(&texture).unwrap();
let mut image = textures.get_mut(&texture).unwrap();
image.sampler = sampling.unwrap_or_default();

(texture_atlas_layout, texture_atlas_sources, texture)
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/animated_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ fn animate_materials(
mut materials: ResMut<Assets<StandardMaterial>>,
) {
for material_handle in material_handles.iter() {
if let Some(material) = materials.get_mut(material_handle)
if let Some(mut material) = materials.get_mut(material_handle)
&& let Color::Hsla(ref mut hsla) = material.base_color
{
*hsla = hsla.rotate_hue(time.delta_secs() * 100.0);
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/blend_modes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ fn example_control_system(
let randomize_colors = input.just_pressed(KeyCode::KeyC);

for (material_handle, controls) in &controllable {
let material = materials.get_mut(material_handle).unwrap();
let mut material = materials.get_mut(material_handle).unwrap();

if controls.color && randomize_colors {
material.base_color = Srgba {
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/generate_custom_mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ fn input_handler(
if keyboard_input.just_pressed(KeyCode::Space) {
let mesh_handle = mesh_query.single().expect("Query not successful");
let mesh = meshes.get_mut(mesh_handle).unwrap();
toggle_texture(mesh);
toggle_texture(mesh.into_inner());
}
if keyboard_input.pressed(KeyCode::KeyX) {
for mut transform in &mut query {
Expand Down
4 changes: 2 additions & 2 deletions examples/3d/reflection_probes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ impl FromWorld for Cubemaps {
}

fn setup_environment_map_usage(cubemaps: Res<Cubemaps>, mut images: ResMut<Assets<Image>>) {
if let Some(image) = images.get_mut(&cubemaps.specular_environment_map)
if let Some(mut image) = images.get_mut(&cubemaps.specular_environment_map)
&& !image
.texture_descriptor
.usage
Expand Down Expand Up @@ -415,7 +415,7 @@ fn change_sphere_roughness(

// Update the sphere material
for material_handle in sphere_query.iter() {
if let Some(material) = materials.get_mut(&material_handle.0) {
if let Some(mut material) = materials.get_mut(&material_handle.0) {
material.perceptual_roughness = app_status.sphere_roughness;
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/tonemapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ fn drag_drop_image(
};

for mat_h in &image_mat {
if let Some(mat) = materials.get_mut(mat_h) {
if let Some(mut mat) = materials.get_mut(mat_h) {
mat.base_color_texture = Some(new_image.clone());

// Despawn the image viewer instructions
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/transmission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ fn example_control_system(
let randomize_colors = input.just_pressed(KeyCode::KeyC);

for (material_handle, controls) in &controllable {
let material = materials.get_mut(material_handle).unwrap();
let mut material = materials.get_mut(material_handle).unwrap();
if controls.specular_transmission {
material.specular_transmission = state.specular_transmission;
material.thickness = state.thickness;
Expand Down
2 changes: 1 addition & 1 deletion examples/animation/animated_mesh_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ fn setup_scene_once_loaded(
AnimationNodeType::Clip(handle) => clips.get_mut(handle),
_ => unreachable!(),
};
clip.unwrap()
clip.unwrap().into_inner()
}

for (entity, mut player) in &mut players {
Expand Down
2 changes: 1 addition & 1 deletion examples/animation/animation_masks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ fn handle_button_toggles(
// iterate just for clarity's sake.)
for animation_graph_handle in animation_players.iter_mut() {
// The animation graph needs to have loaded.
let Some(animation_graph) = animation_graphs.get_mut(animation_graph_handle) else {
let Some(mut animation_graph) = animation_graphs.get_mut(animation_graph_handle) else {
continue;
};

Expand Down
2 changes: 1 addition & 1 deletion examples/app/headless_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ fn update(
if !image_data.is_empty() {
for image in images_to_save.iter() {
// Fill correct data from channel to image
let img_bytes = images.get_mut(image.id()).unwrap();
let mut img_bytes = images.get_mut(image.id()).unwrap();

// We need to ensure that this works regardless of the image dimensions
// If the image became wider when copying from the texture to the buffer,
Expand Down
2 changes: 1 addition & 1 deletion examples/asset/alter_mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ fn alter_mesh(
mut meshes: ResMut<Assets<Mesh>>,
) {
// Obtain a mutable reference to the Mesh asset.
let Some(mesh) = meshes.get_mut(*left_shape) else {
let Some(mut mesh) = meshes.get_mut(*left_shape) else {
return;
};

Expand Down
2 changes: 1 addition & 1 deletion examples/asset/alter_sprite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ fn alter_handle(

fn alter_asset(mut images: ResMut<Assets<Image>>, left_bird: Single<&Sprite, With<Left>>) {
// Obtain a mutable reference to the Image asset.
let Some(image) = images.get_mut(&left_bird.image) else {
let Some(mut image) = images.get_mut(&left_bird.image) else {
return;
};

Expand Down
2 changes: 1 addition & 1 deletion examples/gltf/edit_material_on_gltf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ fn change_material(
// If you intend on creating multiple models with the same tint, it
// is best to cache the handle somewhere, as having multiple materials
// that are identical is expensive
let mut new_material = material.clone();
let mut new_material = material.into_inner_untracked().clone();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem like we should be using into_inner_untracked here. It seems more like we should stop using get_mut at all for this material.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're correct, it looks like get_mut here was redundant.

new_material.base_color = color_override.0;

// Override `MeshMaterial3d` with new material
Expand Down
4 changes: 2 additions & 2 deletions examples/gltf/query_gltf_primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ fn find_top_material_and_mesh(
for (mat_handle, mesh_handle, name) in mat_query.iter() {
// locate a material by material name
if name.0 == "Top" {
if let Some(material) = materials.get_mut(mat_handle) {
if let Some(mut material) = materials.get_mut(mat_handle) {
if let Color::Hsla(ref mut hsla) = material.base_color {
*hsla = hsla.rotate_hue(time.delta_secs() * 100.0);
} else {
material.base_color = Color::from(Hsla::hsl(0.0, 0.9, 0.7));
}
}

if let Some(mesh) = meshes.get_mut(mesh_handle)
if let Some(mut mesh) = meshes.get_mut(mesh_handle)
&& let Some(VertexAttributeValues::Float32x3(positions)) =
mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)
{
Expand Down
2 changes: 1 addition & 1 deletion examples/shader/shader_prepass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ fn toggle_prepass_view(
color.0 = Color::WHITE;
});

let mat = materials.get_mut(*material_handle).unwrap();
let mut mat = materials.get_mut(*material_handle).unwrap();
mat.settings.show_depth = (*prepass_view == 1) as u32;
mat.settings.show_normals = (*prepass_view == 2) as u32;
mat.settings.show_motion_vectors = (*prepass_view == 3) as u32;
Expand Down
2 changes: 1 addition & 1 deletion examples/shader/storage_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ fn update(
) {
let material = materials.get_mut(&material_handles.0).unwrap();

let buffer = buffers.get_mut(&material.colors).unwrap();
let mut buffer = buffers.get_mut(&material.colors).unwrap();
buffer.set_data(
(0..5)
.map(|i| {
Expand Down
2 changes: 1 addition & 1 deletion examples/stress_tests/many_materials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ fn animate_materials(
mut materials: ResMut<Assets<StandardMaterial>>,
) {
for (i, material_handle) in material_handles.iter().enumerate() {
if let Some(material) = materials.get_mut(material_handle) {
if let Some(mut material) = materials.get_mut(material_handle) {
let color = Color::hsl(
((i as f32 * 2.345 + time.elapsed_secs()) * 100.0) % 360.0,
1.0,
Expand Down
Loading
Loading