From 855753b4d30643b08a9d6802511e56952f182373 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Mon, 7 Oct 2024 21:58:50 -0700 Subject: [PATCH 01/27] Remove all the `get_mut` functions from `Assets`. Removing these functions makes it simpler to redesign them for when assets are Arc'd. --- crates/bevy_asset/src/assets.rs | 43 --------------------------------- 1 file changed, 43 deletions(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index f16feebdc1856..c33f13f09de7b 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -214,20 +214,6 @@ impl DenseAssetStorage { } } - pub(crate) fn get_mut(&mut self, index: AssetIndex) -> Option<&mut A> { - let entry = self.storage.get_mut(index.index as usize)?; - match entry { - Entry::None => None, - Entry::Some { value, generation } => { - if *generation == index.generation { - value.as_mut() - } else { - None - } - } - } - } - pub(crate) fn flush(&mut self) { // NOTE: this assumes the allocator index is monotonically increasing. let new_len = self @@ -330,20 +316,6 @@ impl Assets { } } - /// Retrieves an [`Asset`] stored for the given `id` if it exists. If it does not exist, it will be inserted using `insert_fn`. - // PERF: Optimize this or remove it - pub fn get_or_insert_with( - &mut self, - id: impl Into>, - insert_fn: impl FnOnce() -> A, - ) -> &mut A { - let id: AssetId = id.into(); - if self.get(id).is_none() { - self.insert(id, insert_fn()); - } - self.get_mut(id).unwrap() - } - /// Returns `true` if the `id` exists in this collection. Otherwise it returns `false`. pub fn contains(&self, id: impl Into>) -> bool { match id.into() { @@ -419,21 +391,6 @@ impl Assets { } } - /// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists. - /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. - #[inline] - pub fn get_mut(&mut self, id: impl Into>) -> Option<&mut A> { - let id: AssetId = 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 - } - /// Removes (and returns) the [`Asset`] with the given `id`, if it exists. /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. pub fn remove(&mut self, id: impl Into>) -> Option { From 226391005ff46168899ec5eb6d6dce8e88e121f8 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Mon, 7 Oct 2024 22:01:50 -0700 Subject: [PATCH 02/27] Make `Assets` store assets as `Arc` instead of just `A`. --- crates/bevy_asset/src/assets.rs | 57 +++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index c33f13f09de7b..1d8b352b64a8b 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -106,7 +106,10 @@ enum Entry { #[default] None, /// Some is an indicator that there is a live handle active for the entry at this [`AssetIndex`] - Some { value: Option, generation: u32 }, + Some { + value: Option>, + generation: u32, + }, } /// Stores [`Asset`] values in a Vec-like storage identified by [`AssetIndex`]. @@ -141,7 +144,7 @@ impl DenseAssetStorage { pub(crate) fn insert( &mut self, index: AssetIndex, - asset: A, + asset: Arc, ) -> Result { self.flush(); let entry = &mut self.storage[index.index as usize]; @@ -166,7 +169,7 @@ impl DenseAssetStorage { /// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists). /// This will recycle the id and allow new entries to be inserted. - pub(crate) fn remove_dropped(&mut self, index: AssetIndex) -> Option { + pub(crate) fn remove_dropped(&mut self, index: AssetIndex) -> Option> { self.remove_internal(index, |dense_storage| { dense_storage.storage[index.index as usize] = Entry::None; dense_storage.allocator.recycle(index); @@ -176,7 +179,7 @@ impl DenseAssetStorage { /// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists). /// This will _not_ recycle the id. New values with the current ID can still be inserted. The ID will /// not be reused until [`DenseAssetStorage::remove_dropped`] is called. - pub(crate) fn remove_still_alive(&mut self, index: AssetIndex) -> Option { + pub(crate) fn remove_still_alive(&mut self, index: AssetIndex) -> Option> { self.remove_internal(index, |_| {}) } @@ -184,7 +187,7 @@ impl DenseAssetStorage { &mut self, index: AssetIndex, removed_action: impl FnOnce(&mut Self), - ) -> Option { + ) -> Option> { self.flush(); let value = match &mut self.storage[index.index as usize] { Entry::None => return None, @@ -200,7 +203,8 @@ impl DenseAssetStorage { value } - pub(crate) fn get(&self, index: AssetIndex) -> Option<&A> { + /// Gets a borrow to the [`Arc`]d asset. + pub(crate) fn get_arc(&self, index: AssetIndex) -> Option<&Arc> { let entry = self.storage.get(index.index as usize)?; match entry { Entry::None => None, @@ -269,7 +273,7 @@ impl DenseAssetStorage { #[derive(Resource)] pub struct Assets { dense_storage: DenseAssetStorage, - hash_map: HashMap, + hash_map: HashMap>, handle_provider: AssetHandleProvider, queued_events: Vec>, /// Assets managed by the `Assets` struct with live strong `Handle`s @@ -305,7 +309,8 @@ impl Assets { } /// Inserts the given `asset`, identified by the given `id`. If an asset already exists for `id`, it will be replaced. - pub fn insert(&mut self, id: impl Into>, asset: A) { + pub fn insert(&mut self, id: impl Into>, asset: impl Into>) { + let asset = asset.into(); match id.into() { AssetId::Index { index, .. } => { self.insert_with_index(index, asset).unwrap(); @@ -319,12 +324,12 @@ impl Assets { /// Returns `true` if the `id` exists in this collection. Otherwise it returns `false`. pub fn contains(&self, id: impl Into>) -> bool { match id.into() { - AssetId::Index { index, .. } => self.dense_storage.get(index).is_some(), + AssetId::Index { index, .. } => self.dense_storage.get_arc(index).is_some(), AssetId::Uuid { uuid } => self.hash_map.contains_key(&uuid), } } - pub(crate) fn insert_with_uuid(&mut self, uuid: Uuid, asset: A) -> Option { + pub(crate) fn insert_with_uuid(&mut self, uuid: Uuid, asset: Arc) -> Option> { let result = self.hash_map.insert(uuid, asset); if result.is_some() { self.queued_events @@ -338,7 +343,7 @@ impl Assets { pub(crate) fn insert_with_index( &mut self, index: AssetIndex, - asset: A, + asset: Arc, ) -> Result { let replaced = self.dense_storage.insert(index, asset)?; if replaced { @@ -353,7 +358,7 @@ impl Assets { /// Adds the given `asset` and allocates a new strong [`Handle`] for it. #[inline] - pub fn add(&mut self, asset: impl Into) -> Handle { + pub fn add(&mut self, asset: impl Into>) -> Handle { let index = self.dense_storage.allocator.reserve(); self.insert_with_index(index, asset.into()).unwrap(); Handle::Strong( @@ -386,14 +391,24 @@ impl Assets { #[inline] pub fn get(&self, id: impl Into>) -> Option<&A> { match id.into() { - AssetId::Index { index, .. } => self.dense_storage.get(index), - AssetId::Uuid { uuid } => self.hash_map.get(&uuid), + AssetId::Index { index, .. } => self.dense_storage.get_arc(index).map(|a| &**a), + AssetId::Uuid { uuid } => self.hash_map.get(&uuid).map(|a| &**a), + } + } + + /// Retrieves the [`Arc`] of an [`Asset`] with the given `id`, if it exists. + /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. + #[inline] + pub fn get_arc(&self, id: impl Into>) -> Option> { + match id.into() { + AssetId::Index { index, .. } => self.dense_storage.get_arc(index).cloned(), + AssetId::Uuid { uuid } => self.hash_map.get(&uuid).cloned(), } } /// Removes (and returns) the [`Asset`] with the given `id`, if it exists. /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. - pub fn remove(&mut self, id: impl Into>) -> Option { + pub fn remove(&mut self, id: impl Into>) -> Option> { let id: AssetId = id.into(); let result = self.remove_untracked(id); if result.is_some() { @@ -404,7 +419,7 @@ impl Assets { /// Removes (and returns) the [`Asset`] with the given `id`, if it exists. This skips emitting [`AssetEvent::Removed`]. /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. - pub fn remove_untracked(&mut self, id: impl Into>) -> Option { + pub fn remove_untracked(&mut self, id: impl Into>) -> Option> { let id: AssetId = id.into(); self.duplicate_handles.remove(&id); match id { @@ -450,7 +465,7 @@ impl Assets { /// Returns an iterator over the [`AssetId`] and [`Asset`] ref of every asset in this collection. // PERF: this could be accelerated if we implement a skip list. Consider the cost/benefits - pub fn iter(&self) -> impl Iterator, &A)> { + pub fn iter(&self) -> impl Iterator, Arc)> + '_ { self.dense_storage .storage .iter() @@ -465,13 +480,13 @@ impl Assets { }, marker: PhantomData, }; - (id, v) + (id, v.clone()) }), }) .chain( self.hash_map .iter() - .map(|(i, v)| (AssetId::Uuid { uuid: *i }, v)), + .map(|(i, v)| (AssetId::Uuid { uuid: *i }, v.clone())), ) } @@ -549,11 +564,11 @@ impl Assets { pub struct AssetsMutIterator<'a, A: Asset> { queued_events: &'a mut Vec>, dense_storage: Enumerate>>, - hash_map: bevy_platform_support::collections::hash_map::IterMut<'a, Uuid, A>, + hash_map: bevy_platform_support::collections::hash_map::IterMut<'a, Uuid, Arc>, } impl<'a, A: Asset> Iterator for AssetsMutIterator<'a, A> { - type Item = (AssetId, &'a mut A); + type Item = (AssetId, &'a mut Arc); fn next(&mut self) -> Option { for (i, entry) in &mut self.dense_storage { From 68efabd5897bd46761d74c097766faae58610d9f Mon Sep 17 00:00:00 2001 From: andriyDev Date: Tue, 8 Oct 2024 23:13:43 -0700 Subject: [PATCH 03/27] Separate out inserting/adding an owned asset from the arc version. Many callers expect to be able to call `meshes.add(Rectangle::new(50.0, 100.0))`. Now we can keep all these callsites the same (while still supporting inserting/adding `Arc`s directly). --- crates/bevy_asset/src/assets.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 1d8b352b64a8b..4f384aac63950 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -308,8 +308,15 @@ impl Assets { self.handle_provider.reserve_handle().typed::() } - /// Inserts the given `asset`, identified by the given `id`. If an asset already exists for `id`, it will be replaced. - pub fn insert(&mut self, id: impl Into>, asset: impl Into>) { + /// Inserts the given `asset`, identified by the given `id`. If an asset already exists for + /// `id`, it will be replaced. + pub fn insert(&mut self, id: impl Into>, asset: A) { + self.insert_arc(id, asset); + } + + /// Inserts the given [`Arc`]-ed `asset`, identified by the given `id`. If an asset already + /// exists for `id`, it will be replaced. + pub fn insert_arc(&mut self, id: impl Into>, asset: impl Into>) { let asset = asset.into(); match id.into() { AssetId::Index { index, .. } => { @@ -358,7 +365,13 @@ impl Assets { /// Adds the given `asset` and allocates a new strong [`Handle`] for it. #[inline] - pub fn add(&mut self, asset: impl Into>) -> Handle { + pub fn add(&mut self, asset: impl Into) -> Handle { + self.add_arc(asset.into()) + } + + /// Adds the given [`Arc`]-ed `asset` and allocates a new strong [`Handle`] for it. + #[inline] + pub fn add_arc(&mut self, asset: impl Into>) -> Handle { let index = self.dense_storage.allocator.reserve(); self.insert_with_index(index, asset.into()).unwrap(); Handle::Strong( From d2931992f53911904fc4191778ce6c48670a7130 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Tue, 8 Oct 2024 00:29:32 -0700 Subject: [PATCH 04/27] Implement `get_cloned_mut`. Now for assets that impl `Clone`, we can get a mutable borrow to the asset always by cloning. --- crates/bevy_asset/src/assets.rs | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 4f384aac63950..35859509b8546 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -218,6 +218,21 @@ impl DenseAssetStorage { } } + /// Gets a mutable borrow to the [`Arc`]d asset. + /// + /// Returns [`None`] if the asset is missing, or has a different generation. + pub(crate) fn get_arc_mut(&mut self, index: AssetIndex) -> Option<&mut Arc> { + let entry = self.storage.get_mut(index.index as usize)?; + let Entry::Some { value, generation } = entry else { + return None; + }; + + if *generation != index.generation { + return None; + } + value.as_mut() + } + pub(crate) fn flush(&mut self) { // NOTE: this assumes the allocator index is monotonically increasing. let new_len = self @@ -573,6 +588,26 @@ impl Assets { } } +impl Assets { + /// Retrieves a mutable reference to the [`Asset`] with the given `id` if it exists. + /// + /// If the asset is currently aliased (another [`Arc`] or [`Weak`] to this asset exists), the + /// asset is cloned. + /// + /// [`Weak`]: std::sync::Weak + pub fn get_cloned_mut(&mut self, id: impl Into>) -> Option<&mut A> { + let id = id.into(); + let arc = match id { + AssetId::Index { index, .. } => self.dense_storage.get_arc_mut(index)?, + AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid)?, + }; + + self.queued_events.push(AssetEvent::Modified { id }); + // This clones the asset if the asset is aliased. + Some(Arc::make_mut(arc)) + } +} + /// A mutable iterator over [`Assets`]. pub struct AssetsMutIterator<'a, A: Asset> { queued_events: &'a mut Vec>, From b93a63d081b1397ac6b4080fc675134b6af63fb6 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Mon, 7 Oct 2024 23:18:29 -0700 Subject: [PATCH 05/27] Implement `get_inplace_mut`. Now we can mutate assets in place as long as there are no more Arcs or Weaks for that asset. --- crates/bevy_asset/src/assets.rs | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 35859509b8546..4ca8048d8a869 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -434,6 +434,33 @@ impl Assets { } } + /// Retrieves a mutable reference to the [`Asset`] with the given `id` if it exists. + /// + /// If the asset is currently aliased (another [`Arc`] or [`Weak`] to this asset exists), + /// returns an error. + /// + /// [`Weak`]: std::sync::Weak + pub fn get_inplace_mut( + &mut self, + id: impl Into>, + ) -> Result<&mut A, MutableAssetError> { + let id = id.into(); + let arc = match id { + AssetId::Index { index, .. } => self.dense_storage.get_arc_mut(index), + AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid), + }; + + let Some(arc) = arc else { + return Err(MutableAssetError::Missing); + }; + let Some(asset_mut) = Arc::get_mut(arc) else { + return Err(MutableAssetError::Aliased); + }; + + self.queued_events.push(AssetEvent::Modified { id }); + Ok(asset_mut) + } + /// Removes (and returns) the [`Asset`] with the given `id`, if it exists. /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. pub fn remove(&mut self, id: impl Into>) -> Option> { @@ -656,6 +683,14 @@ pub struct InvalidGenerationError { current_generation: u32, } +#[derive(Error, Display, Debug)] +pub enum MutableAssetError { + #[display("The asset is not present or has an invalid generation.")] + Missing, + #[display("The asset Arc is aliased (there is another Arc or Weak to this asset), so it is not safe to mutate.")] + Aliased, +} + #[cfg(test)] mod test { use crate::AssetIndex; From 8818bbb36b2f87429a7c60b964f0f0c68790fb05 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Tue, 8 Oct 2024 17:08:03 -0700 Subject: [PATCH 06/27] Implement `get_reflect_cloned_mut`. This is basically the same as `get_cloned_mut` but using reflection. This is not really intended to be used publicly, but the reflect.rs needs a clone that only relies on reflection, so making it public seems fine. --- crates/bevy_asset/src/assets.rs | 36 ++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 4ca8048d8a869..bca3795f104f3 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -7,7 +7,7 @@ use bevy_ecs::{ system::{Res, ResMut, SystemChangeTick}, }; use bevy_platform_support::collections::HashMap; -use bevy_reflect::{Reflect, TypePath}; +use bevy_reflect::{FromReflect, Reflect, TypePath}; use core::{any::TypeId, iter::Enumerate, marker::PhantomData, sync::atomic::AtomicU32}; use crossbeam_channel::{Receiver, Sender}; use serde::{Deserialize, Serialize}; @@ -635,6 +635,40 @@ impl Assets { } } +impl Assets { + /// Retrieves a mutable reference to the [`Asset`] with the given `id` if it exists. + /// + /// If the asset is currently aliased (another [`Arc`] or [`Weak`] to this asset exists), the + /// asset is "cloned" using reflection. + /// + /// [`Weak`]: std::sync::Weak + pub fn get_reflect_cloned_mut(&mut self, id: impl Into>) -> Option<&mut A> { + let id = id.into(); + let arc = match id { + AssetId::Index { index, .. } => self.dense_storage.get_arc_mut(index)?, + AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid)?, + }; + + self.queued_events.push(AssetEvent::Modified { id }); + if Arc::get_mut(arc).is_some() { + // This is a workaround to the lack of polonius (the problem described at + // https://rust-lang.github.io/rfcs/2094-nll.html#problem-case-3-conditional-control-flow-across-functions) + // Since we can get mutable access to the `Arc` and the value inside the `Arc`, it is + // impossible for us to "lose" access in between these calls. + return Some( + Arc::get_mut(arc) + .expect("the Arc is aliased somehow even though we just got it mutably in `Assets::get_reflect_cloned_mut`."), + ); + } + + let cloned_asset = FromReflect::from_reflect(arc.as_ref()).expect( + "could not call `FromReflect::from_reflect` in `Assets::get_reflect_cloned_mut`", + ); + *arc = Arc::new(cloned_asset); + Some(Arc::get_mut(arc).expect("the Arc is aliased somehow even though we just cloned it in `Assets::get_reflect_cloned_mut`.")) + } +} + /// A mutable iterator over [`Assets`]. pub struct AssetsMutIterator<'a, A: Asset> { queued_events: &'a mut Vec>, From d4b20c129beeff3a0eccb86b9ba5a277d2d9b753 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Tue, 8 Oct 2024 17:12:07 -0700 Subject: [PATCH 07/27] Update most calls in bevy_asset to either use a `.into()` or one of the `get_*_mut` variants. --- crates/bevy_asset/src/direct_access_ext.rs | 13 +++++++++++ crates/bevy_asset/src/lib.rs | 2 +- crates/bevy_asset/src/loader.rs | 4 +++- crates/bevy_asset/src/reflect.rs | 26 +++++++++++++++++----- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/crates/bevy_asset/src/direct_access_ext.rs b/crates/bevy_asset/src/direct_access_ext.rs index bfa7fa17b29c0..e164987ace894 100644 --- a/crates/bevy_asset/src/direct_access_ext.rs +++ b/crates/bevy_asset/src/direct_access_ext.rs @@ -1,6 +1,8 @@ //! Add methods on `World` to simplify loading assets when all //! you have is a `World`. +use alloc::sync::Arc; + use bevy_ecs::world::World; use crate::{meta::Settings, Asset, AssetPath, AssetServer, Assets, Handle}; @@ -9,6 +11,9 @@ pub trait DirectAssetAccessExt { /// Insert an asset similarly to [`Assets::add`]. fn add_asset(&mut self, asset: impl Into) -> Handle; + /// Insert an [`Arc`]ed asset similarly to [`Assets::add_arc`]. + fn add_arc_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; @@ -28,6 +33,14 @@ impl DirectAssetAccessExt for World { self.resource_mut::>().add(asset) } + /// Insert an [`Arc`]ed asset similarly to [`Assets::add_arc`]. + /// + /// # Panics + /// If `self` doesn't have an [`AssetServer`] resource initialized yet. + fn add_arc_asset(&mut self, asset: impl Into>) -> Handle { + self.resource_mut::>().add_arc(asset) + } + /// Load an asset similarly to [`AssetServer::load`]. /// /// # Panics diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index f0b8e92a9320d..af8da8962b131 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -1100,7 +1100,7 @@ mod tests { { let mut texts = app.world_mut().resource_mut::>(); - let a = texts.get_mut(a_id).unwrap(); + let a = texts.get_inplace_mut(a_id).unwrap(); a.text = "Changed".to_string(); } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 3be672e0c194d..d7e33e1980705 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -252,7 +252,9 @@ impl_downcast!(AssetContainer); impl AssetContainer for A { fn insert(self: Box, id: UntypedAssetId, world: &mut World) { - world.resource_mut::>().insert(id.typed(), *self); + world + .resource_mut::>() + .insert_arc(id.typed(), self); } fn asset_type_name(&self) -> &'static str { diff --git a/crates/bevy_asset/src/reflect.rs b/crates/bevy_asset/src/reflect.rs index 5c436c10610f0..66f1e698ecb8a 100644 --- a/crates/bevy_asset/src/reflect.rs +++ b/crates/bevy_asset/src/reflect.rs @@ -1,4 +1,4 @@ -use alloc::boxed::Box; +use alloc::{boxed::Box, sync::Arc}; use core::any::{Any, TypeId}; use bevy_ecs::world::{unsafe_world_cell::UnsafeWorldCell, World}; @@ -19,6 +19,7 @@ pub struct ReflectAsset { assets_resource_type_id: TypeId, get: fn(&World, UntypedHandle) -> Option<&dyn Reflect>, + get_arc: fn(&World, UntypedHandle) -> Option>, // SAFETY: // - may only be called with an [`UnsafeWorldCell`] which can be used to access the corresponding `Assets` resource mutably // - may only be used to access **at most one** access at once @@ -46,7 +47,12 @@ impl ReflectAsset { (self.get)(world, handle) } - /// Equivalent of [`Assets::get_mut`] + /// Equivalent of [`Assets::get_arc`] + pub fn get_arc(&self, world: &World, handle: UntypedHandle) -> Option> { + (self.get_arc)(world, handle) + } + + /// Equivalent of [`Assets::get_reflect_cloned_mut`] pub fn get_mut<'w>( &self, world: &'w mut World, @@ -62,7 +68,7 @@ impl ReflectAsset { } } - /// Equivalent of [`Assets::get_mut`], but works with an [`UnsafeWorldCell`]. + /// Equivalent of [`Assets::get_reflect_cloned_mut`], but works with an [`UnsafeWorldCell`]. /// /// Only use this method when you have ensured that you are the *only* one with access to the [`Assets`] resource of the asset type. /// Furthermore, this does *not* allow you to have look up two distinct handles, @@ -140,15 +146,23 @@ impl FromType for ReflectAsset { get: |world, handle| { let assets = world.resource::>(); let asset = assets.get(&handle.typed_debug_checked()); - asset.map(|asset| asset as &dyn Reflect) + asset.map(|asset| asset as _) + }, + get_arc: |world, handle| { + let assets = world.resource::>(); + let asset = assets.get_arc(&handle.typed_debug_checked()); + asset.map(|asset| asset as _) }, get_unchecked_mut: |world, handle| { // SAFETY: `get_unchecked_mut` must be called with `UnsafeWorldCell` having access to `Assets`, // and must ensure to only have at most one reference to it live at all times. #[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")] let assets = unsafe { world.get_resource_mut::>().unwrap().into_inner() }; - let asset = assets.get_mut(&handle.typed_debug_checked()); - asset.map(|asset| asset as &mut dyn Reflect) + + let handle = handle.typed_debug_checked(); + assets + .get_reflect_cloned_mut(&handle) + .map(|asset| asset as _) }, add: |world, value| { let mut assets = world.resource_mut::>(); From f4a1e2760ebfbf27cd0b2dbd9ad9d2883b5fddbf Mon Sep 17 00:00:00 2001 From: andriyDev Date: Tue, 8 Oct 2024 21:50:20 -0700 Subject: [PATCH 08/27] Make RenderAssets take an `Arc` to the source asset and extract source assets as Arcs. --- crates/bevy_render/src/mesh/mod.rs | 4 +++- crates/bevy_render/src/render_asset.rs | 16 +++++++++------- crates/bevy_render/src/storage.rs | 8 +++++--- crates/bevy_render/src/texture/gpu_image.rs | 17 ++++++++--------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 40fb08d987bc6..8bb0826e14636 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -1,3 +1,5 @@ +use alloc::sync::Arc; + use bevy_math::Vec3; pub use bevy_mesh::*; use morph::{MeshMorphWeights, MorphWeights}; @@ -206,7 +208,7 @@ impl RenderAsset for RenderMesh { /// Converts the extracted mesh into a [`RenderMesh`]. fn prepare_asset( - mesh: Self::SourceAsset, + mesh: Arc, _: AssetId, (images, ref mut mesh_vertex_buffer_layouts): &mut SystemParamItem, ) -> Result> { diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index 043b3554a148a..93eed9e5adbee 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -1,6 +1,7 @@ use crate::{ render_resource::AsBindGroupError, ExtractSchedule, MainWorld, Render, RenderApp, RenderSet, }; +use alloc::sync::Arc; use bevy_app::{App, Plugin, SubApp}; pub use bevy_asset::RenderAssetUsages; use bevy_asset::{Asset, AssetEvent, AssetId, Assets}; @@ -19,7 +20,7 @@ use tracing::{debug, error}; #[derive(Debug, Error)] pub enum PrepareAssetError { #[error("Failed to prepare asset")] - RetryNextUpdate(E), + RetryNextUpdate(Arc), #[error("Failed to build bind group: {0}")] AsBindGroupError(AsBindGroupError), } @@ -63,9 +64,10 @@ pub trait RenderAsset: Send + Sync + 'static + Sized { /// Prepares the [`RenderAsset::SourceAsset`] for the GPU by transforming it into a [`RenderAsset`]. /// - /// ECS data may be accessed via `param`. + /// ECS data may be accessed via `param`. If you want to move the `source_asset`s fields, + /// consider using [`Arc::try_unwrap`] (and falling back to cloning the asset otherwise). fn prepare_asset( - source_asset: Self::SourceAsset, + source_asset: Arc, asset_id: AssetId, param: &mut SystemParamItem, ) -> Result>; @@ -150,7 +152,7 @@ impl RenderAssetDependency for A { #[derive(Resource)] pub struct ExtractedAssets { /// The assets extracted this frame. - pub extracted: Vec<(AssetId, A::SourceAsset)>, + pub extracted: Vec<(AssetId, Arc)>, /// IDs of the assets removed this frame. /// @@ -260,8 +262,8 @@ pub(crate) fn extract_render_asset( let mut extracted_assets = Vec::new(); let mut added = >::default(); for id in changed_assets.drain() { - if let Some(asset) = assets.get(id) { - let asset_usage = A::asset_usage(asset); + if let Some(asset) = assets.get_arc(id) { + let asset_usage = A::asset_usage(&asset); if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) { if asset_usage == RenderAssetUsages::RENDER_WORLD { if let Some(asset) = assets.remove(id) { @@ -290,7 +292,7 @@ pub(crate) fn extract_render_asset( /// All assets that should be prepared next frame. #[derive(Resource)] pub struct PrepareNextFrameAssets { - assets: Vec<(AssetId, A::SourceAsset)>, + assets: Vec<(AssetId, Arc)>, } impl Default for PrepareNextFrameAssets { diff --git a/crates/bevy_render/src/storage.rs b/crates/bevy_render/src/storage.rs index 7434f3999f3dc..309dc2987b735 100644 --- a/crates/bevy_render/src/storage.rs +++ b/crates/bevy_render/src/storage.rs @@ -1,3 +1,5 @@ +use alloc::sync::Arc; + use crate::{ render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssetUsages}, render_resource::{Buffer, BufferUsages}, @@ -113,15 +115,15 @@ impl RenderAsset for GpuShaderStorageBuffer { } fn prepare_asset( - source_asset: Self::SourceAsset, + source_asset: Arc, _: AssetId, render_device: &mut SystemParamItem, ) -> Result> { - match source_asset.data { + match source_asset.data.as_ref() { Some(data) => { let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { label: source_asset.buffer_description.label, - contents: &data, + contents: data, usage: source_asset.buffer_description.usage, }); Ok(GpuShaderStorageBuffer { buffer }) diff --git a/crates/bevy_render/src/texture/gpu_image.rs b/crates/bevy_render/src/texture/gpu_image.rs index 551bd3ee02e09..51f0a04e7ca43 100644 --- a/crates/bevy_render/src/texture/gpu_image.rs +++ b/crates/bevy_render/src/texture/gpu_image.rs @@ -1,3 +1,5 @@ +use alloc::sync::Arc; + use crate::{ render_asset::{PrepareAssetError, RenderAsset, RenderAssetUsages}, render_resource::{DefaultImageSampler, Sampler, Texture, TextureView}, @@ -41,7 +43,7 @@ impl RenderAsset for GpuImage { /// Converts the extracted image into a [`GpuImage`]. fn prepare_asset( - image: Self::SourceAsset, + image: Arc, _: AssetId, (render_device, render_queue, default_sampler): &mut SystemParamItem, ) -> Result> { @@ -57,14 +59,11 @@ impl RenderAsset for GpuImage { render_device.create_texture(&image.texture_descriptor) }; - let texture_view = texture.create_view( - image - .texture_view_descriptor - .or_else(|| Some(TextureViewDescriptor::default())) - .as_ref() - .unwrap(), - ); - let sampler = match image.sampler { + let texture_view = match image.texture_view_descriptor.as_ref() { + None => texture.create_view(&TextureViewDescriptor::default()), + Some(descriptor) => texture.create_view(descriptor), + }; + let sampler = match &image.sampler { ImageSampler::Default => (***default_sampler).clone(), ImageSampler::Descriptor(descriptor) => { render_device.create_sampler(&descriptor.as_wgpu()) From 14809232eee3123a3a6667ecb5e15ec881e351c0 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Wed, 9 Oct 2024 00:43:19 -0700 Subject: [PATCH 09/27] Make all the crates use the new `get_*_mut` variants, or use `Arc` in `RenderAsset`. --- .../src/auto_exposure/compensation_curve.rs | 4 +++- crates/bevy_core_pipeline/src/lib.rs | 2 ++ crates/bevy_gizmos/src/lib.rs | 7 +++++-- crates/bevy_pbr/src/material.rs | 3 ++- crates/bevy_pbr/src/wireframe.rs | 2 +- crates/bevy_sprite/src/mesh2d/material.rs | 3 ++- crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 2 +- crates/bevy_text/src/font_atlas.rs | 5 +++-- crates/bevy_ui/src/lib.rs | 2 ++ crates/bevy_ui/src/render/ui_material_pipeline.rs | 3 ++- 10 files changed, 23 insertions(+), 10 deletions(-) diff --git a/crates/bevy_core_pipeline/src/auto_exposure/compensation_curve.rs b/crates/bevy_core_pipeline/src/auto_exposure/compensation_curve.rs index 7a89de331c19c..3e40c13cc75b3 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/compensation_curve.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/compensation_curve.rs @@ -1,3 +1,5 @@ +use alloc::sync::Arc; + use bevy_asset::prelude::*; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_math::{cubic_splines::CubicGenerator, FloatExt, Vec2}; @@ -193,7 +195,7 @@ impl RenderAsset for GpuAutoExposureCompensationCurve { } fn prepare_asset( - source: Self::SourceAsset, + source: Arc, _: AssetId, (render_device, render_queue): &mut SystemParamItem, ) -> Result> { diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 49b9b7a20b2ee..0f27ddf240fef 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -6,6 +6,8 @@ html_favicon_url = "https://bevyengine.org/assets/icon.png" )] +extern crate alloc; + pub mod auto_exposure; pub mod blit; pub mod bloom; diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index fdc2243233916..55a8de55eaa37 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -125,6 +125,9 @@ use { bytemuck::cast_slice, }; +extern crate alloc; + +use alloc::sync::Arc; #[cfg(all( feature = "bevy_render", any(feature = "bevy_pbr", feature = "bevy_sprite"), @@ -394,7 +397,7 @@ fn update_gizmo_meshes( handles.handles.insert(TypeId::of::(), None); } else if let Some(handle) = handles.handles.get_mut(&TypeId::of::()) { if let Some(handle) = handle { - let gizmo = gizmo_assets.get_mut(handle.id()).unwrap(); + let gizmo = gizmo_assets.get_cloned_mut(handle.id()).unwrap(); gizmo.buffer.list_positions = mem::take(&mut storage.list_positions); gizmo.buffer.list_colors = mem::take(&mut storage.list_colors); @@ -554,7 +557,7 @@ impl RenderAsset for GpuLineGizmo { type Param = SRes; fn prepare_asset( - gizmo: Self::SourceAsset, + gizmo: Arc, _: AssetId, render_device: &mut SystemParamItem, ) -> Result> { diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index ba44d3ecc8744..3e30eaa0df433 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -5,6 +5,7 @@ use crate::meshlet::{ InstanceManager, }; use crate::*; +use alloc::sync::Arc; use bevy_asset::prelude::AssetChanged; use bevy_asset::{Asset, AssetEvents, AssetId, AssetServer, UntypedAssetId}; use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred}; @@ -1170,7 +1171,7 @@ impl RenderAsset for PreparedMaterial { ); fn prepare_asset( - material: Self::SourceAsset, + material: Arc, material_id: AssetId, ( render_device, diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 68862bbf711ed..a6c54a99dfff3 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -123,7 +123,7 @@ fn global_color_changed( mut materials: ResMut>, global_material: Res, ) { - if let Some(global_material) = materials.get_mut(&global_material.handle) { + if let Some(global_material) = materials.get_cloned_mut(&global_material.handle) { global_material.color = config.default_color.into(); } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index c5bc522131a34..dd3a570dd0a55 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -2,6 +2,7 @@ use crate::{ DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, SetMesh2dBindGroup, SetMesh2dViewBindGroup, ViewKeyCache, ViewSpecializationTicks, }; +use alloc::sync::Arc; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::prelude::AssetChanged; use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEvents, AssetId, AssetServer, Handle}; @@ -897,7 +898,7 @@ impl RenderAsset for PreparedMaterial2d { ); fn prepare_asset( - material: Self::SourceAsset, + material: Arc, _: AssetId, ( render_device, diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 4547f83d9b6af..83ef909f0efc3 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -120,7 +120,7 @@ fn global_color_changed( mut materials: ResMut>, global_material: Res, ) { - if let Some(global_material) = materials.get_mut(&global_material.handle) { + if let Some(global_material) = materials.get_cloned_mut(&global_material.handle) { global_material.color = config.default_color.into(); } } diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index e14157cb38f8a..2b11c5312aeec 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -94,8 +94,9 @@ impl FontAtlas { texture: &Image, offset: IVec2, ) -> Result<(), TextError> { - let atlas_layout = atlas_layouts.get_mut(&self.texture_atlas).unwrap(); - let atlas_texture = textures.get_mut(&self.texture).unwrap(); + // Accessing these assets mutably (especially the texture) could incur a large cloning cost. + let atlas_layout = atlas_layouts.get_cloned_mut(&self.texture_atlas).unwrap(); + let atlas_texture = textures.get_cloned_mut(&self.texture).unwrap(); if let Ok(glyph_index) = self.dynamic_texture_atlas_builder diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index fd573f1c0c54a..87862418ee350 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -10,6 +10,8 @@ //! Spawn UI elements with [`widget::Button`], [`ImageNode`], [`Text`](prelude::Text) and [`Node`] //! This UI is laid out with the Flexbox and CSS Grid layout models (see ) +extern crate alloc; + pub mod measurement; pub mod ui_material; pub mod update; diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 202f3749222f5..c1ac6f7b71a1d 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -1,3 +1,4 @@ +use alloc::sync::Arc; use core::{hash::Hash, marker::PhantomData, ops::Range}; use crate::*; @@ -595,7 +596,7 @@ impl RenderAsset for PreparedUiMaterial { type Param = (SRes, SRes>, M::Param); fn prepare_asset( - material: Self::SourceAsset, + material: Arc, _: AssetId, (render_device, pipeline, ref mut material_param): &mut SystemParamItem, ) -> Result> { From 901d843cb86cc06f1073fc8de3a5bcc5383833a0 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Wed, 9 Oct 2024 00:52:28 -0700 Subject: [PATCH 10/27] Update the examples to use the `get_*_mut` variants or otherwise dealing with `Arc`s. --- examples/2d/cpu_draw.rs | 4 +++- examples/2d/texture_atlas.rs | 2 +- examples/3d/animated_material.rs | 2 +- examples/3d/blend_modes.rs | 2 +- examples/3d/depth_of_field.rs | 5 ++++- examples/3d/generate_custom_mesh.rs | 2 +- examples/3d/lightmaps.rs | 15 ++++++++++++--- examples/3d/parallax_mapping.rs | 8 ++++---- examples/3d/query_gltf_primitives.rs | 4 ++-- examples/3d/skybox.rs | 2 +- examples/3d/tonemapping.rs | 2 +- examples/3d/transmission.rs | 2 +- examples/3d/transparency_3d.rs | 4 +++- examples/animation/animated_mesh_events.rs | 2 +- examples/animation/animation_masks.rs | 3 ++- examples/app/headless_renderer.rs | 2 +- examples/asset/alter_mesh.rs | 2 +- examples/asset/alter_sprite.rs | 2 +- examples/asset/asset_decompression.rs | 7 +++++-- examples/shader/array_texture.rs | 2 +- examples/shader/shader_prepass.rs | 2 +- examples/shader/storage_buffer.rs | 4 ++-- .../tools/scene_viewer/scene_viewer_plugin.rs | 4 +++- examples/ui/ui_material.rs | 2 +- 24 files changed, 54 insertions(+), 32 deletions(-) diff --git a/examples/2d/cpu_draw.rs b/examples/2d/cpu_draw.rs index adf7f63b90c44..40db1893f9b3b 100644 --- a/examples/2d/cpu_draw.rs +++ b/examples/2d/cpu_draw.rs @@ -105,7 +105,9 @@ fn draw( } // Get the image from Bevy's asset storage. - let image = images.get_mut(&my_handle.0).expect("Image not found"); + let image = images + .get_cloned_mut(&my_handle.0) + .expect("Image not found"); // Compute the position of the pixel to draw. diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index 7510afbceea74..19456c866681f 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -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 image = textures.get_cloned_mut(&texture).unwrap(); image.sampler = sampling.unwrap_or_default(); (texture_atlas_layout, texture_atlas_sources, texture) diff --git a/examples/3d/animated_material.rs b/examples/3d/animated_material.rs index a4d6046ff08f5..f7a907d63a239 100644 --- a/examples/3d/animated_material.rs +++ b/examples/3d/animated_material.rs @@ -50,7 +50,7 @@ fn animate_materials( mut materials: ResMut>, ) { for material_handle in material_handles.iter() { - if let Some(material) = materials.get_mut(material_handle) { + if let Some(material) = materials.get_cloned_mut(material_handle) { if let Color::Hsla(ref mut hsla) = material.base_color { *hsla = hsla.rotate_hue(time.delta_secs() * 100.0); } diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs index 830acfdb34160..7d3fc72c5bb01 100644 --- a/examples/3d/blend_modes.rs +++ b/examples/3d/blend_modes.rs @@ -270,7 +270,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 material = materials.get_cloned_mut(material_handle).unwrap(); if controls.color && randomize_colors { material.base_color = Srgba { diff --git a/examples/3d/depth_of_field.rs b/examples/3d/depth_of_field.rs index d6ca77bbde0ca..68d7fa56df17b 100644 --- a/examples/3d/depth_of_field.rs +++ b/examples/3d/depth_of_field.rs @@ -198,7 +198,10 @@ fn tweak_scene( // Add a nice lightmap to the circuit board. for (entity, name, material) in named_entities.iter_mut() { if &**name == "CircuitBoard" { - materials.get_mut(material).unwrap().lightmap_exposure = 10000.0; + materials + .get_cloned_mut(material) + .unwrap() + .lightmap_exposure = 10000.0; commands.entity(entity).insert(Lightmap { image: asset_server.load("models/DepthOfFieldExample/CircuitBoardLightmap.hdr"), ..default() diff --git a/examples/3d/generate_custom_mesh.rs b/examples/3d/generate_custom_mesh.rs index 2222486ba1da7..5b1201e2a1845 100644 --- a/examples/3d/generate_custom_mesh.rs +++ b/examples/3d/generate_custom_mesh.rs @@ -79,7 +79,7 @@ fn input_handler( ) { if keyboard_input.just_pressed(KeyCode::Space) { let mesh_handle = mesh_query.get_single().expect("Query not successful"); - let mesh = meshes.get_mut(mesh_handle).unwrap(); + let mesh = meshes.get_cloned_mut(mesh_handle).unwrap(); toggle_texture(mesh); } if keyboard_input.pressed(KeyCode::KeyX) { diff --git a/examples/3d/lightmaps.rs b/examples/3d/lightmaps.rs index 975b37d7f2873..9c73aa5588f2a 100644 --- a/examples/3d/lightmaps.rs +++ b/examples/3d/lightmaps.rs @@ -71,7 +71,10 @@ fn add_lightmaps_to_meshes( let exposure = 250.0; for (entity, name, material) in meshes.iter() { if &**name == "large_box" { - materials.get_mut(material).unwrap().lightmap_exposure = exposure; + materials + .get_cloned_mut(material) + .unwrap() + .lightmap_exposure = exposure; commands.entity(entity).insert(Lightmap { image: asset_server.load("lightmaps/CornellBox-Large.zstd.ktx2"), bicubic_sampling: args.bicubic, @@ -81,7 +84,10 @@ fn add_lightmaps_to_meshes( } if &**name == "small_box" { - materials.get_mut(material).unwrap().lightmap_exposure = exposure; + materials + .get_cloned_mut(material) + .unwrap() + .lightmap_exposure = exposure; commands.entity(entity).insert(Lightmap { image: asset_server.load("lightmaps/CornellBox-Small.zstd.ktx2"), bicubic_sampling: args.bicubic, @@ -91,7 +97,10 @@ fn add_lightmaps_to_meshes( } if name.starts_with("cornell_box") { - materials.get_mut(material).unwrap().lightmap_exposure = exposure; + materials + .get_cloned_mut(material) + .unwrap() + .lightmap_exposure = exposure; commands.entity(entity).insert(Lightmap { image: asset_server.load("lightmaps/CornellBox-Box.zstd.ktx2"), bicubic_sampling: args.bicubic, diff --git a/examples/3d/parallax_mapping.rs b/examples/3d/parallax_mapping.rs index 47d83a4e76960..61f9ca77adb8a 100644 --- a/examples/3d/parallax_mapping.rs +++ b/examples/3d/parallax_mapping.rs @@ -1,7 +1,7 @@ //! A simple 3D scene with a spinning cube with a normal map and depth map to demonstrate parallax mapping. //! Press left mouse button to cycle through different views. -use std::fmt; +use std::{fmt, sync::Arc}; use bevy::{image::ImageLoaderSettings, math::ops, prelude::*}; @@ -97,7 +97,7 @@ fn update_parallax_depth_scale( for (_, mat) in materials.iter_mut() { let current_depth = mat.parallax_depth_scale; let new_depth = current_depth.lerp(target_depth.0, DEPTH_CHANGE_RATE); - mat.parallax_depth_scale = new_depth; + Arc::make_mut(mat).parallax_depth_scale = new_depth; *writer.text(*text, 1) = format!("Parallax depth scale: {new_depth:.5}\n"); if (new_depth - current_depth).abs() <= 0.000000001 { *depth_update = false; @@ -122,7 +122,7 @@ fn switch_method( *writer.text(text_entity, 3) = format!("Method: {}\n", *current); for (_, mat) in materials.iter_mut() { - mat.parallax_mapping_method = current.0; + Arc::make_mut(mat).parallax_mapping_method = current.0; } } @@ -146,7 +146,7 @@ fn update_parallax_layers( *writer.text(text_entity, 2) = format!("Layers: {layer_count:.0}\n"); for (_, mat) in materials.iter_mut() { - mat.max_parallax_layer_count = layer_count; + Arc::make_mut(mat).max_parallax_layer_count = layer_count; } } diff --git a/examples/3d/query_gltf_primitives.rs b/examples/3d/query_gltf_primitives.rs index 780abeb977aae..d68a493ca1ae9 100644 --- a/examples/3d/query_gltf_primitives.rs +++ b/examples/3d/query_gltf_primitives.rs @@ -26,7 +26,7 @@ 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(material) = materials.get_cloned_mut(mat_handle) { if let Color::Hsla(ref mut hsla) = material.base_color { *hsla = hsla.rotate_hue(time.delta_secs() * 100.0); } else { @@ -34,7 +34,7 @@ fn find_top_material_and_mesh( } } - if let Some(mesh) = meshes.get_mut(mesh_handle) { + if let Some(mesh) = meshes.get_cloned_mut(mesh_handle) { if let Some(VertexAttributeValues::Float32x3(positions)) = mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION) { diff --git a/examples/3d/skybox.rs b/examples/3d/skybox.rs index dd797473bf57d..543a577dd2a94 100644 --- a/examples/3d/skybox.rs +++ b/examples/3d/skybox.rs @@ -148,7 +148,7 @@ fn asset_loaded( ) { if !cubemap.is_loaded && asset_server.load_state(&cubemap.image_handle).is_loaded() { info!("Swapping to {}...", CUBEMAPS[cubemap.index].0); - let image = images.get_mut(&cubemap.image_handle).unwrap(); + let image = images.get_cloned_mut(&cubemap.image_handle).unwrap(); // NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture, // so they appear as one texture. The following code reconfigures the texture as necessary. if image.texture_descriptor.array_layer_count() == 1 { diff --git a/examples/3d/tonemapping.rs b/examples/3d/tonemapping.rs index 8fabb0d464dbd..5256a33d427e7 100644 --- a/examples/3d/tonemapping.rs +++ b/examples/3d/tonemapping.rs @@ -208,7 +208,7 @@ fn drag_drop_image( }; for mat_h in &image_mat { - if let Some(mat) = materials.get_mut(mat_h) { + if let Some(mat) = materials.get_cloned_mut(mat_h) { mat.base_color_texture = Some(new_image.clone()); // Despawn the image viewer instructions diff --git a/examples/3d/transmission.rs b/examples/3d/transmission.rs index 42f05ac2fac08..bdc3f9d29dec3 100644 --- a/examples/3d/transmission.rs +++ b/examples/3d/transmission.rs @@ -439,7 +439,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 material = materials.get_cloned_mut(material_handle).unwrap(); if controls.specular_transmission { material.specular_transmission = state.specular_transmission; material.thickness = state.thickness; diff --git a/examples/3d/transparency_3d.rs b/examples/3d/transparency_3d.rs index 35cb7c1d14f25..8f39b8cab1970 100644 --- a/examples/3d/transparency_3d.rs +++ b/examples/3d/transparency_3d.rs @@ -2,6 +2,8 @@ //! Shows the effects of different blend modes. //! The `fade_transparency` system smoothly changes the transparency over time. +use std::sync::Arc; + use bevy::{math::ops, prelude::*}; fn main() { @@ -110,6 +112,6 @@ fn setup( pub fn fade_transparency(time: Res)>, ) { for (entity, Compressed { compressed, .. }) in query.iter() { - let Some(GzAsset { uncompressed }) = compressed_assets.remove(compressed) else { + let Some(compressed) = compressed_assets.remove(compressed) else { continue; }; + let GzAsset { uncompressed } = + Arc::into_inner(compressed).expect("The asset Arc is not aliased."); + let uncompressed = uncompressed.take::().unwrap(); commands diff --git a/examples/shader/array_texture.rs b/examples/shader/array_texture.rs index 1d156c66e417e..6e061acab6b00 100644 --- a/examples/shader/array_texture.rs +++ b/examples/shader/array_texture.rs @@ -63,7 +63,7 @@ fn create_array_texture( return; } loading_texture.is_loaded = true; - let image = images.get_mut(&loading_texture.handle).unwrap(); + let image = images.get_cloned_mut(&loading_texture.handle).unwrap(); // Create a new array texture asset from the loaded texture. let array_layers = 4; diff --git a/examples/shader/shader_prepass.rs b/examples/shader/shader_prepass.rs index b53f9fe941729..027938248ed9a 100644 --- a/examples/shader/shader_prepass.rs +++ b/examples/shader/shader_prepass.rs @@ -233,7 +233,7 @@ fn toggle_prepass_view( color.0 = Color::WHITE; }); - let mat = materials.get_mut(*material_handle).unwrap(); + let mat = materials.get_cloned_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; diff --git a/examples/shader/storage_buffer.rs b/examples/shader/storage_buffer.rs index f8e7be074b928..a857618bea611 100644 --- a/examples/shader/storage_buffer.rs +++ b/examples/shader/storage_buffer.rs @@ -73,9 +73,9 @@ fn update( mut materials: ResMut>, mut buffers: ResMut>, ) { - let material = materials.get_mut(&material_handles.0).unwrap(); + let material = materials.get_cloned_mut(&material_handles.0).unwrap(); - let buffer = buffers.get_mut(&material.colors).unwrap(); + let buffer = buffers.get_cloned_mut(&material.colors).unwrap(); buffer.set_data( (0..5) .map(|i| { diff --git a/examples/tools/scene_viewer/scene_viewer_plugin.rs b/examples/tools/scene_viewer/scene_viewer_plugin.rs index 49f4805d06eb7..4a024b64aeeaa 100644 --- a/examples/tools/scene_viewer/scene_viewer_plugin.rs +++ b/examples/tools/scene_viewer/scene_viewer_plugin.rs @@ -113,7 +113,9 @@ fn scene_load_check( scene_handle.scene_index ) }); - let scene = scenes.get_mut(gltf_scene_handle).unwrap(); + let scene = scenes + .get_inplace_mut(gltf_scene_handle) + .expect("The asset is missing or is aliased."); let mut query = scene .world diff --git a/examples/ui/ui_material.rs b/examples/ui/ui_material.rs index 2ad6e5a532478..65da9851697b0 100644 --- a/examples/ui/ui_material.rs +++ b/examples/ui/ui_material.rs @@ -92,7 +92,7 @@ fn animate( ) { let duration = 2.0; for handle in &q { - if let Some(material) = materials.get_mut(handle) { + if let Some(material) = materials.get_cloned_mut(handle) { // rainbow color effect let new_color = Color::hsl((time.elapsed_secs() * 60.0) % 360.0, 1., 0.5); let border_color = Color::hsl((time.elapsed_secs() * 60.0) % 360.0, 0.75, 0.75); From 603ea1b835bbeeba7c340e8f6097554276aa2b6e Mon Sep 17 00:00:00 2001 From: andriyDev Date: Thu, 26 Sep 2024 23:51:35 -0700 Subject: [PATCH 11/27] Create an example for the use of asset arcing. --- Cargo.toml | 11 ++ examples/README.md | 1 + examples/asset/arc_asset.rs | 251 ++++++++++++++++++++++++++++ examples/asset/files/heights.li.ron | 6 + 4 files changed, 269 insertions(+) create mode 100644 examples/asset/arc_asset.rs create mode 100644 examples/asset/files/heights.li.ron diff --git a/Cargo.toml b/Cargo.toml index 383cb50aadd09..ca52f710b5d42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1677,6 +1677,17 @@ description = "Demonstrates various methods to load assets" category = "Assets" wasm = false +[[example]] +name = "arc_asset" +path = "examples/asset/arc_asset.rs" +doc-scrape-examples = true + +[package.metadata.example.arc_asset] +name = "Arced Assets" +description = "Demonstrates how to acquire Arc'd assets and use them in an async context" +category = "Assets" +wasm = false + [[example]] name = "asset_settings" path = "examples/asset/asset_settings.rs" diff --git a/examples/README.md b/examples/README.md index 4569d0ceacefc..dec5632474f5e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -241,6 +241,7 @@ Example | Description --- | --- [Alter Mesh](../examples/asset/alter_mesh.rs) | Shows how to modify the underlying asset of a Mesh after spawning. [Alter Sprite](../examples/asset/alter_sprite.rs) | Shows how to modify texture assets after spawning. +[Arced Assets](../examples/asset/arc_asset.rs) | Demonstrates how to acquire Arc'd assets and use them in an async context [Asset Decompression](../examples/asset/asset_decompression.rs) | Demonstrates loading a compressed asset [Asset Loading](../examples/asset/asset_loading.rs) | Demonstrates various methods to load assets [Asset Processing](../examples/asset/processing/asset_processing.rs) | Demonstrates how to process and load custom assets diff --git a/examples/asset/arc_asset.rs b/examples/asset/arc_asset.rs new file mode 100644 index 0000000000000..d19feb3aee3b6 --- /dev/null +++ b/examples/asset/arc_asset.rs @@ -0,0 +1,251 @@ +//! This example illustrates how to use assets in an async context (through locking). + +use std::sync::Arc; + +use bevy::{ + asset::AssetLoader, + color::palettes::tailwind, + math::FloatOrd, + prelude::*, + render::{mesh::Indices, render_asset::RenderAssetUsages}, + tasks::AsyncComputeTaskPool, +}; +use rand::{Rng, SeedableRng}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +fn main() { + App::new() + .add_plugins( + // This just tells the asset server to look in the right examples folder + DefaultPlugins.set(AssetPlugin { + file_path: "examples/asset/files".to_string(), + ..Default::default() + }), + ) + .init_asset::() + .register_asset_loader(LinearInterpolationLoader) + .add_systems(Startup, setup) + .add_systems(Update, (start_mesh_generation, finish_mesh_generation)) + .run(); +} + +fn setup( + asset_server: Res, + mut materials: ResMut>, + meshes: Res>, + mut commands: Commands, +) { + // Spawn a camera. + commands.spawn(( + Transform::from_translation(Vec3::new(15.0, 15.0, 15.0)).looking_at(Vec3::ZERO, Vec3::Y), + Camera3d::default(), + )); + + // Spawn a light. + commands.spawn(( + Transform::default().looking_to(Dir3::from_xyz(1.0, -1.0, 0.0).unwrap(), Dir3::Y), + DirectionalLight::default(), + )); + + // Spawn the mesh. Reserve the handle so we can generate it later. + let mesh = meshes.reserve_handle(); + commands.spawn(( + Mesh3d(mesh.clone()), + MeshMaterial3d(materials.add(StandardMaterial { + base_color: tailwind::SLATE_100.into(), + ..Default::default() + })), + )); + + // Create the parameters for mesh generation. + commands.insert_resource(MeshGeneration { + height_interpolation: asset_server.load("heights.li.ron"), + mesh, + size: UVec2::new(30, 30), + }); + + // Create the channel we will communicate across. + let (sender, receiver) = crossbeam_channel::bounded(1); + commands.insert_resource(MeshGenerationChannel { sender, receiver }); +} + +#[derive(Resource)] +struct MeshGeneration { + height_interpolation: Handle, + mesh: Handle, + size: UVec2, +} + +#[derive(Resource)] +struct MeshGenerationChannel { + sender: crossbeam_channel::Sender, + receiver: crossbeam_channel::Receiver, +} + +/// Starts a mesh generation task whenever the height interpolation asset is updated. +fn start_mesh_generation( + mut asset_events: EventReader>, + linear_interpolations: Res>, + mesh_generation: Res, + channel: Res, +) { + // Only recompute if the height interpolation asset has changed. + let regenerate_id = mesh_generation.height_interpolation.id(); + let mut recompute = false; + for asset_event in asset_events.read() { + match asset_event { + AssetEvent::Added { id } | AssetEvent::Modified { id } if *id == regenerate_id => { + recompute = true; + } + _ => {} + } + } + + if !recompute { + return; + } + + let task_pool = AsyncComputeTaskPool::get(); + let size = mesh_generation.size; + // Get an `Arc` of the height interpolation asset to pass to the spawned task. + let height_interpolation = linear_interpolations + .get_arc(&mesh_generation.height_interpolation) + .expect("The asset is loaded"); + let channel = channel.sender.clone(); + // Spawn a task to generate the mesh, then send the resulting mesh across the channel. + task_pool + .spawn(async move { + let mesh = generate_mesh(size, height_interpolation); + channel.send(mesh).expect("The channel never closes"); + }) + .detach(); +} + +/// Reads from the mesh generation channel and inserts the mesh asset. +fn finish_mesh_generation( + mesh_generation: Res, + channel: Res, + mut meshes: ResMut>, +) { + let Ok(mesh) = channel.receiver.try_recv() else { + return; + }; + meshes.insert(&mesh_generation.mesh, mesh); +} + +/// A basic linear interpolation curve implementation. +#[derive(Asset, TypePath, Serialize, Deserialize)] +struct LinearInterpolation(Vec<(f32, f32)>); + +impl LinearInterpolation { + /// Samples the linear interpolation at `value`. + fn sample(&self, value: f32) -> f32 { + match self.0.iter().position(|(x, _)| value < *x) { + None => self.0.last().expect("The interpolation is non-empty").1, + Some(0) => self.0.first().expect("The interpolation is non-empty").1, + Some(next) => { + let previous = next - 1; + + let (next_x, next_y) = self.0[next]; + let (previous_x, previous_y) = self.0[previous]; + + let alpha = (value - previous_x) / (next_x - previous_x); + + alpha * (next_y - previous_y) + previous_y + } + } + } +} + +#[derive(Default)] +struct LinearInterpolationLoader; + +#[derive(Debug, Error)] +enum LinearInterpolationLoaderError { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + RonSpannedError(#[from] ron::error::SpannedError), + #[error("The loaded interpolation is empty.")] + Empty, + #[error("The loaded interpolation contains duplicate X values")] + DuplicateXValues, +} + +impl AssetLoader for LinearInterpolationLoader { + type Asset = LinearInterpolation; + type Settings = (); + type Error = LinearInterpolationLoaderError; + + async fn load( + &self, + reader: &mut dyn bevy::asset::io::Reader, + _settings: &Self::Settings, + _load_context: &mut bevy::asset::LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + let mut interpolation: LinearInterpolation = ron::de::from_bytes(&bytes)?; + if interpolation.0.is_empty() { + return Err(Self::Error::Empty); + } + interpolation.0.sort_by_key(|(key, _)| FloatOrd(*key)); + if interpolation + .0 + .windows(2) + .any(|window| window[0].0 == window[1].0) + { + return Err(Self::Error::DuplicateXValues); + } + Ok(interpolation) + } + + fn extensions(&self) -> &[&str] { + &["li.ron"] + } +} + +/// Generates the mesh given the interpolation curve and the size of the mesh. +fn generate_mesh(size: UVec2, interpolation: Arc) -> Mesh { + let mut rng = rand_chacha::ChaChaRng::seed_from_u64(12345); + + let center = Vec3::new((size.x as f32) / 2.0, 0.0, (size.y as f32) / -2.0); + + let mut vertices = Vec::with_capacity(((size.x + 1) * (size.y + 1)) as usize); + let mut uvs = Vec::with_capacity(((size.x + 1) * (size.y + 1)) as usize); + for y in 0..size.y + 1 { + for x in 0..size.x + 1 { + let height = interpolation.sample(rng.gen()); + vertices.push(Vec3::new(x as f32, height, -(y as f32)) - center); + uvs.push(Vec2::new(x as f32, -(y as f32))); + } + } + + let y_stride = size.x + 1; + let mut indices = Vec::with_capacity((size.x * size.y * 6) as usize); + for y in 0..size.y { + for x in 0..size.x { + indices.push(x + y * y_stride); + indices.push(x + 1 + y * y_stride); + indices.push(x + 1 + (y + 1) * y_stride); + indices.push(x + y * y_stride); + indices.push(x + 1 + (y + 1) * y_stride); + indices.push(x + (y + 1) * y_stride); + } + } + + let mut mesh = Mesh::new( + bevy_render::mesh::PrimitiveTopology::TriangleList, + RenderAssetUsages::RENDER_WORLD, + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_indices(Indices::U32(indices)); + + mesh.compute_normals(); + mesh.generate_tangents() + .expect("The tangents are well formed"); + + mesh +} diff --git a/examples/asset/files/heights.li.ron b/examples/asset/files/heights.li.ron new file mode 100644 index 0000000000000..8d4e03f71a21e --- /dev/null +++ b/examples/asset/files/heights.li.ron @@ -0,0 +1,6 @@ +([ + (0.0, 0.0), + (0.5, 0.1), + (0.7, 1.0), + (1.0, 2.0), +]) \ No newline at end of file From 4822dbcaf5b71c1344a0141e95ee5547451cd8ba Mon Sep 17 00:00:00 2001 From: andriyDev Date: Wed, 9 Oct 2024 23:40:23 -0700 Subject: [PATCH 12/27] Add a nice big warning on `get_arc` about holding the `Arc` for long periods of time. --- crates/bevy_asset/src/assets.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index bca3795f104f3..6b6875defc78e 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -425,7 +425,14 @@ impl Assets { } /// Retrieves the [`Arc`] of an [`Asset`] with the given `id`, if it exists. - /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. + /// + /// Note that this supports anything that implements `Into>`, which includes + /// [`Handle`] and [`AssetId`]. Be careful with holding the Arc indefinitely: holding the + /// [`Arc`] (or a [`Weak`]) prevents the asset from being mutated in place. This can incur + /// clones when using `get_cloned_mut`, or can just entirely block mutation when using + /// `get_inplace_mut`. + /// + /// [`Weak`]: std::sync::Weak #[inline] pub fn get_arc(&self, id: impl Into>) -> Option> { match id.into() { From a9341f3975f7a3e112e1cb32973462b7ab2e461c Mon Sep 17 00:00:00 2001 From: andriyDev Date: Fri, 1 Nov 2024 12:18:31 -0700 Subject: [PATCH 13/27] Rewrite any "Arc-ed" to "Arc-d" to make it consistent with other uses. --- Cargo.toml | 2 +- crates/bevy_asset/src/assets.rs | 6 +++--- crates/bevy_asset/src/direct_access_ext.rs | 4 ++-- examples/README.md | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ca52f710b5d42..c2512c103a7cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1683,7 +1683,7 @@ path = "examples/asset/arc_asset.rs" doc-scrape-examples = true [package.metadata.example.arc_asset] -name = "Arced Assets" +name = "Arc'd Assets" description = "Demonstrates how to acquire Arc'd assets and use them in an async context" category = "Assets" wasm = false diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 6b6875defc78e..db6ccc5a8a7bf 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -329,8 +329,8 @@ impl Assets { self.insert_arc(id, asset); } - /// Inserts the given [`Arc`]-ed `asset`, identified by the given `id`. If an asset already - /// exists for `id`, it will be replaced. + /// Inserts the given [`Arc`]d `asset`, identified by the given `id`. If an asset already exists + /// for `id`, it will be replaced. pub fn insert_arc(&mut self, id: impl Into>, asset: impl Into>) { let asset = asset.into(); match id.into() { @@ -384,7 +384,7 @@ impl Assets { self.add_arc(asset.into()) } - /// Adds the given [`Arc`]-ed `asset` and allocates a new strong [`Handle`] for it. + /// Adds the given [`Arc`]d `asset` and allocates a new strong [`Handle`] for it. #[inline] pub fn add_arc(&mut self, asset: impl Into>) -> Handle { let index = self.dense_storage.allocator.reserve(); diff --git a/crates/bevy_asset/src/direct_access_ext.rs b/crates/bevy_asset/src/direct_access_ext.rs index e164987ace894..6ce9e995c7b21 100644 --- a/crates/bevy_asset/src/direct_access_ext.rs +++ b/crates/bevy_asset/src/direct_access_ext.rs @@ -11,7 +11,7 @@ pub trait DirectAssetAccessExt { /// Insert an asset similarly to [`Assets::add`]. fn add_asset(&mut self, asset: impl Into) -> Handle; - /// Insert an [`Arc`]ed asset similarly to [`Assets::add_arc`]. + /// Insert an [`Arc`]d asset similarly to [`Assets::add_arc`]. fn add_arc_asset(&mut self, asset: impl Into>) -> Handle; /// Load an asset similarly to [`AssetServer::load`]. @@ -33,7 +33,7 @@ impl DirectAssetAccessExt for World { self.resource_mut::>().add(asset) } - /// Insert an [`Arc`]ed asset similarly to [`Assets::add_arc`]. + /// Insert an [`Arc`]d asset similarly to [`Assets::add_arc`]. /// /// # Panics /// If `self` doesn't have an [`AssetServer`] resource initialized yet. diff --git a/examples/README.md b/examples/README.md index dec5632474f5e..6657e206416a1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -241,7 +241,7 @@ Example | Description --- | --- [Alter Mesh](../examples/asset/alter_mesh.rs) | Shows how to modify the underlying asset of a Mesh after spawning. [Alter Sprite](../examples/asset/alter_sprite.rs) | Shows how to modify texture assets after spawning. -[Arced Assets](../examples/asset/arc_asset.rs) | Demonstrates how to acquire Arc'd assets and use them in an async context +[Arc'd Assets](../examples/asset/arc_asset.rs) | Demonstrates how to acquire Arc'd assets and use them in an async context [Asset Decompression](../examples/asset/asset_decompression.rs) | Demonstrates loading a compressed asset [Asset Loading](../examples/asset/asset_loading.rs) | Demonstrates various methods to load assets [Asset Processing](../examples/asset/processing/asset_processing.rs) | Demonstrates how to process and load custom assets From add3c665a215314d34a92bc0b301354ba8931241 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Fri, 1 Nov 2024 12:24:36 -0700 Subject: [PATCH 14/27] Improve the MutableAssetError messages to match Rust guidelines. https://rust-lang.github.io/api-guidelines/interoperability.html?highlight=error#error-types-are-meaningful-and-well-behaved-c-good-err --- crates/bevy_asset/src/assets.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index db6ccc5a8a7bf..55ed4792f50ab 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -726,9 +726,9 @@ pub struct InvalidGenerationError { #[derive(Error, Display, Debug)] pub enum MutableAssetError { - #[display("The asset is not present or has an invalid generation.")] + #[display("asset is not present or has an invalid generation")] Missing, - #[display("The asset Arc is aliased (there is another Arc or Weak to this asset), so it is not safe to mutate.")] + #[display("asset `Arc` is aliased (there is another `Arc` or `Weak` to this asset), so it is not safe to mutate")] Aliased, } From f6a65056c347f7873ff474977a6e5a1f0a491d34 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Fri, 1 Nov 2024 12:27:51 -0700 Subject: [PATCH 15/27] Rename `get_inplace_mut` to `get_in_place_mut`. --- crates/bevy_asset/src/assets.rs | 4 ++-- crates/bevy_asset/src/lib.rs | 2 +- examples/tools/scene_viewer/scene_viewer_plugin.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 55ed4792f50ab..79f9234331a6d 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -430,7 +430,7 @@ impl Assets { /// [`Handle`] and [`AssetId`]. Be careful with holding the Arc indefinitely: holding the /// [`Arc`] (or a [`Weak`]) prevents the asset from being mutated in place. This can incur /// clones when using `get_cloned_mut`, or can just entirely block mutation when using - /// `get_inplace_mut`. + /// `get_in_place_mut`. /// /// [`Weak`]: std::sync::Weak #[inline] @@ -447,7 +447,7 @@ impl Assets { /// returns an error. /// /// [`Weak`]: std::sync::Weak - pub fn get_inplace_mut( + pub fn get_in_place_mut( &mut self, id: impl Into>, ) -> Result<&mut A, MutableAssetError> { diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index af8da8962b131..8f372af259560 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -1100,7 +1100,7 @@ mod tests { { let mut texts = app.world_mut().resource_mut::>(); - let a = texts.get_inplace_mut(a_id).unwrap(); + let a = texts.get_in_place_mut(a_id).unwrap(); a.text = "Changed".to_string(); } diff --git a/examples/tools/scene_viewer/scene_viewer_plugin.rs b/examples/tools/scene_viewer/scene_viewer_plugin.rs index 4a024b64aeeaa..695d7586495d5 100644 --- a/examples/tools/scene_viewer/scene_viewer_plugin.rs +++ b/examples/tools/scene_viewer/scene_viewer_plugin.rs @@ -114,7 +114,7 @@ fn scene_load_check( ) }); let scene = scenes - .get_inplace_mut(gltf_scene_handle) + .get_in_place_mut(gltf_scene_handle) .expect("The asset is missing or is aliased."); let mut query = scene From d13ca5fdac3a9ac42a71a9615eee31eb961dfb71 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Fri, 1 Nov 2024 12:29:36 -0700 Subject: [PATCH 16/27] Fix the example using bad wording. --- examples/asset/arc_asset.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/asset/arc_asset.rs b/examples/asset/arc_asset.rs index d19feb3aee3b6..1ce360c664c73 100644 --- a/examples/asset/arc_asset.rs +++ b/examples/asset/arc_asset.rs @@ -1,4 +1,5 @@ -//! This example illustrates how to use assets in an async context (through locking). +//! This example illustrates how to use assets in an async context (through cloning the underlying +//! `Arc`). use std::sync::Arc; From 88098ce7b9c659de3961b56425d800859d716b20 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Tue, 10 Dec 2024 22:32:48 -0800 Subject: [PATCH 17/27] Switch MutableAssetError to use thiserror instead of derive_more. --- crates/bevy_asset/src/assets.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 79f9234331a6d..82caef51eff37 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -724,11 +724,11 @@ pub struct InvalidGenerationError { current_generation: u32, } -#[derive(Error, Display, Debug)] +#[derive(Error, Debug)] pub enum MutableAssetError { - #[display("asset is not present or has an invalid generation")] + #[error("asset is not present or has an invalid generation")] Missing, - #[display("asset `Arc` is aliased (there is another `Arc` or `Weak` to this asset), so it is not safe to mutate")] + #[error("asset `Arc` is aliased (there is another `Arc` or `Weak` to this asset), so it is not safe to mutate")] Aliased, } From e794441aed6f747d60000596f2d1ccbb85f44822 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Sun, 29 Dec 2024 13:16:12 -0800 Subject: [PATCH 18/27] Fix the `asset_changed` test by using `get_inplace_mut`. --- crates/bevy_asset/src/asset_changed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index f11283f4882f9..d616acd145687 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -355,7 +355,7 @@ mod tests { .iter() .find_map(|(h, a)| (a.0 == i).then_some(h)) .unwrap(); - let asset = assets.get_mut(id).unwrap(); + let asset = assets.get_in_place_mut(id).unwrap(); println!("setting new value for {}", asset.0); asset.1 = "new_value"; }; From 3dc1a8e607cdc517e9d21c8b78e26eac9372dc7d Mon Sep 17 00:00:00 2001 From: andriyDev Date: Sun, 29 Dec 2024 13:20:04 -0800 Subject: [PATCH 19/27] Make the docs for `AssetChanged` reference `get_cloned_mut` instead of the old `get_mut`. --- crates/bevy_asset/src/asset_changed.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index d616acd145687..da7b4baadc178 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -92,9 +92,9 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { /// /// Unlike `Changed`, this is true whenever the asset for the `A` /// in `ResMut>` changed. For example, when a mesh changed through the -/// [`Assets::get_mut`] method, `AssetChanged` will iterate over all -/// entities with the `Handle` for that mesh. Meanwhile, `Changed>` -/// will iterate over no entities. +/// [`Assets::get_cloned_mut`] method, `AssetChanged` will iterate +/// over all entities with the `Handle` for that mesh. Meanwhile, +/// `Changed>` will iterate over no entities. /// /// Swapping the actual `A` component is a common pattern. So you /// should check for _both_ `AssetChanged` and `Changed` with @@ -121,7 +121,7 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { /// If no `A` asset updated since the last time the system ran, then no lookups occur. /// /// [`AssetEvents`]: crate::AssetEvents -/// [`Assets::get_mut`]: crate::Assets::get_mut +/// [`Assets::get_cloned_mut`]: crate::Assets::get_cloned_mut pub struct AssetChanged(PhantomData); /// [`WorldQuery`] fetch for [`AssetChanged`]. From f02d5581b0885394072458c70f247a1b1e6000f8 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Sun, 29 Dec 2024 13:28:31 -0800 Subject: [PATCH 20/27] Fix the `mixed_lighting` example to use `get_cloned_mut`. --- examples/3d/mixed_lighting.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/3d/mixed_lighting.rs b/examples/3d/mixed_lighting.rs index d827549824723..5b0b5d4755e33 100644 --- a/examples/3d/mixed_lighting.rs +++ b/examples/3d/mixed_lighting.rs @@ -257,7 +257,7 @@ fn update_lightmaps( } // Lightmap exposure defaults to zero, so we need to set it. - if let Some(ref mut material) = materials.get_mut(material) { + if let Some(ref mut material) = materials.get_cloned_mut(material) { material.lightmap_exposure = LIGHTMAP_EXPOSURE; } @@ -280,7 +280,7 @@ fn update_lightmaps( // Add lightmaps to or remove lightmaps from the sphere. if &**name == "Sphere" { // Lightmap exposure defaults to zero, so we need to set it. - if let Some(ref mut material) = materials.get_mut(material) { + if let Some(ref mut material) = materials.get_cloned_mut(material) { material.lightmap_exposure = LIGHTMAP_EXPOSURE; } From 07931ad49c9ed238fed2708750bb5577a9b32998 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Wed, 5 Feb 2025 19:40:40 -0800 Subject: [PATCH 21/27] Fix the `many_materials` and `edit_material_on_gltf` examples. --- examples/3d/edit_material_on_gltf.rs | 2 +- examples/stress_tests/many_materials.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/3d/edit_material_on_gltf.rs b/examples/3d/edit_material_on_gltf.rs index f9de5842a92fe..3df15092b7e10 100644 --- a/examples/3d/edit_material_on_gltf.rs +++ b/examples/3d/edit_material_on_gltf.rs @@ -75,7 +75,7 @@ fn change_material( if let Some(material) = mesh_materials .get(descendants) .ok() - .and_then(|id| asset_materials.get_mut(id.id())) + .and_then(|id| asset_materials.get_cloned_mut(id.id())) { // Create a copy of the material and override base color // If you intend on creating multiple models with the same tint, it diff --git a/examples/stress_tests/many_materials.rs b/examples/stress_tests/many_materials.rs index f2af7fb3827fc..e5af966d0a581 100644 --- a/examples/stress_tests/many_materials.rs +++ b/examples/stress_tests/many_materials.rs @@ -91,7 +91,7 @@ fn animate_materials( mut materials: ResMut>, ) { for (i, material_handle) in material_handles.iter().enumerate() { - if let Some(material) = materials.get_mut(material_handle) { + if let Some(material) = materials.get_cloned_mut(material_handle) { let color = Color::hsl( ((i as f32 * 2.345 + time.elapsed_secs()) * 100.0) % 360.0, 1.0, From 7eca4aaef1fbc3d1e2010da07feaaa4676e95217 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Wed, 5 Feb 2025 19:49:32 -0800 Subject: [PATCH 22/27] Fix the `specular_tint` example. --- examples/3d/specular_tint.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/3d/specular_tint.rs b/examples/3d/specular_tint.rs index 5dc362b9c12e6..87866f75139ce 100644 --- a/examples/3d/specular_tint.rs +++ b/examples/3d/specular_tint.rs @@ -156,7 +156,7 @@ fn shift_hue( app_status.hue += HUE_SHIFT_SPEED; for material_handle in objects_with_materials.iter() { - let Some(material) = standard_materials.get_mut(material_handle) else { + let Some(material) = standard_materials.get_cloned_mut(material_handle) else { continue; }; material.specular_tint = Color::hsva(app_status.hue, 1.0, 1.0, 1.0); @@ -195,7 +195,7 @@ fn toggle_specular_map( }; for material_handle in objects_with_materials.iter() { - let Some(material) = standard_materials.get_mut(material_handle) else { + let Some(material) = standard_materials.get_cloned_mut(material_handle) else { continue; }; From bda9f50e5ee160c27da5fd6234965e0cc9324854 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Sat, 1 Mar 2025 18:11:48 -0800 Subject: [PATCH 23/27] Replace `gen` with `r#gen` to transition to edition 2024. --- examples/asset/arc_asset.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/asset/arc_asset.rs b/examples/asset/arc_asset.rs index 1ce360c664c73..66a57120e6b39 100644 --- a/examples/asset/arc_asset.rs +++ b/examples/asset/arc_asset.rs @@ -217,7 +217,7 @@ fn generate_mesh(size: UVec2, interpolation: Arc) -> Mesh { let mut uvs = Vec::with_capacity(((size.x + 1) * (size.y + 1)) as usize); for y in 0..size.y + 1 { for x in 0..size.x + 1 { - let height = interpolation.sample(rng.gen()); + let height = interpolation.sample(rng.r#gen()); vertices.push(Vec3::new(x as f32, height, -(y as f32)) - center); uvs.push(Vec2::new(x as f32, -(y as f32))); } From b8a3e1f4cb5cb69e41cf622bdea257a4cc769072 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Sat, 1 Mar 2025 20:30:15 -0800 Subject: [PATCH 24/27] Fix the many_cubes stress test to use Arc::make_mut for the materials. --- examples/stress_tests/many_cubes.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/stress_tests/many_cubes.rs b/examples/stress_tests/many_cubes.rs index 1d8a50313152f..a41e6e299cf44 100644 --- a/examples/stress_tests/many_cubes.rs +++ b/examples/stress_tests/many_cubes.rs @@ -8,7 +8,7 @@ //! //! See `cargo run --example many_cubes --release -- --help` for more options. -use std::{f64::consts::PI, str::FromStr}; +use std::{f64::consts::PI, str::FromStr, sync::Arc}; use argh::FromArgs; use bevy::{ @@ -489,7 +489,7 @@ fn update_materials(mut materials: ResMut>, time: Res Date: Sun, 2 Mar 2025 18:26:09 -0800 Subject: [PATCH 25/27] Rename `heights.li.ron` to `arc_asset_heights.li.ron`. --- examples/asset/arc_asset.rs | 2 +- .../asset/files/{heights.li.ron => arc_asset_heights.li.ron} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/asset/files/{heights.li.ron => arc_asset_heights.li.ron} (100%) diff --git a/examples/asset/arc_asset.rs b/examples/asset/arc_asset.rs index 66a57120e6b39..b8218221e8777 100644 --- a/examples/asset/arc_asset.rs +++ b/examples/asset/arc_asset.rs @@ -61,7 +61,7 @@ fn setup( // Create the parameters for mesh generation. commands.insert_resource(MeshGeneration { - height_interpolation: asset_server.load("heights.li.ron"), + height_interpolation: asset_server.load("arc_asset_heights.li.ron"), mesh, size: UVec2::new(30, 30), }); diff --git a/examples/asset/files/heights.li.ron b/examples/asset/files/arc_asset_heights.li.ron similarity index 100% rename from examples/asset/files/heights.li.ron rename to examples/asset/files/arc_asset_heights.li.ron From 338aa26e1bc2db4fa2fdf5d2ba9e405fc3d206fc Mon Sep 17 00:00:00 2001 From: andriyDev Date: Mon, 3 Mar 2025 19:19:39 -0800 Subject: [PATCH 26/27] Rename `arc_asset` example to `access_asset_from_async`. --- Cargo.toml | 10 +++++----- examples/README.md | 2 +- .../asset/{arc_asset.rs => access_asset_from_async.rs} | 2 +- ...s.li.ron => access_asset_from_async_heights.li.ron} | 0 4 files changed, 7 insertions(+), 7 deletions(-) rename examples/asset/{arc_asset.rs => access_asset_from_async.rs} (98%) rename examples/asset/files/{arc_asset_heights.li.ron => access_asset_from_async_heights.li.ron} (100%) diff --git a/Cargo.toml b/Cargo.toml index c87e39e918673..c24d1780db311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1695,13 +1695,13 @@ category = "Assets" wasm = false [[example]] -name = "arc_asset" -path = "examples/asset/arc_asset.rs" +name = "access_asset_from_async" +path = "examples/asset/access_asset_from_async.rs" doc-scrape-examples = true -[package.metadata.example.arc_asset] -name = "Arc'd Assets" -description = "Demonstrates how to acquire Arc'd assets and use them in an async context" +[package.metadata.example.access_asset_from_async] +name = "Access Asset from Async" +description = "Demonstrates how to access and use assets in an async context" category = "Assets" wasm = false diff --git a/examples/README.md b/examples/README.md index aac917fe71a34..9982cdb667fc3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -240,9 +240,9 @@ Example | Description Example | Description --- | --- +[Access Asset from Async](../examples/asset/access_asset_from_async.rs) | Demonstrates how to access and use assets in an async context [Alter Mesh](../examples/asset/alter_mesh.rs) | Shows how to modify the underlying asset of a Mesh after spawning. [Alter Sprite](../examples/asset/alter_sprite.rs) | Shows how to modify texture assets after spawning. -[Arc'd Assets](../examples/asset/arc_asset.rs) | Demonstrates how to acquire Arc'd assets and use them in an async context [Asset Decompression](../examples/asset/asset_decompression.rs) | Demonstrates loading a compressed asset [Asset Loading](../examples/asset/asset_loading.rs) | Demonstrates various methods to load assets [Asset Processing](../examples/asset/processing/asset_processing.rs) | Demonstrates how to process and load custom assets diff --git a/examples/asset/arc_asset.rs b/examples/asset/access_asset_from_async.rs similarity index 98% rename from examples/asset/arc_asset.rs rename to examples/asset/access_asset_from_async.rs index b8218221e8777..cf2f207dc7c8e 100644 --- a/examples/asset/arc_asset.rs +++ b/examples/asset/access_asset_from_async.rs @@ -61,7 +61,7 @@ fn setup( // Create the parameters for mesh generation. commands.insert_resource(MeshGeneration { - height_interpolation: asset_server.load("arc_asset_heights.li.ron"), + height_interpolation: asset_server.load("access_asset_from_async_heights.li.ron"), mesh, size: UVec2::new(30, 30), }); diff --git a/examples/asset/files/arc_asset_heights.li.ron b/examples/asset/files/access_asset_from_async_heights.li.ron similarity index 100% rename from examples/asset/files/arc_asset_heights.li.ron rename to examples/asset/files/access_asset_from_async_heights.li.ron From a97012440ce7068dc36941de64d71c4c15bf2c55 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Sat, 31 May 2025 22:18:02 -0700 Subject: [PATCH 27/27] Fix up a handful of cases not covered by the merge. --- crates/bevy_pbr/src/wireframe.rs | 3 ++- crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 3 ++- crates/bevy_ui/src/widget/viewport.rs | 2 +- examples/shader/shader_material_wesl.rs | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 528eb6e583b52..e9d6907c06ce8 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -22,6 +22,7 @@ use bevy_ecs::{ use bevy_platform::{ collections::{HashMap, HashSet}, hash::FixedHasher, + sync::Arc, }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::camera::extract_cameras; @@ -471,7 +472,7 @@ impl RenderAsset for RenderWireframeMaterial { type Param = (); fn prepare_asset( - source_asset: Self::SourceAsset, + source_asset: Arc, _asset_id: AssetId, _param: &mut SystemParamItem, ) -> Result> { diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index eed7f54a11360..96706f6620281 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -22,6 +22,7 @@ use bevy_ecs::{ use bevy_platform::{ collections::{HashMap, HashSet}, hash::FixedHasher, + sync::Arc, }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ @@ -470,7 +471,7 @@ impl RenderAsset for RenderWireframeMaterial { type Param = (); fn prepare_asset( - source_asset: Self::SourceAsset, + source_asset: Arc, _asset_id: AssetId, _param: &mut SystemParamItem, ) -> Result> { diff --git a/crates/bevy_ui/src/widget/viewport.rs b/crates/bevy_ui/src/widget/viewport.rs index f68033ea7f64a..fd763342dfd7c 100644 --- a/crates/bevy_ui/src/widget/viewport.rs +++ b/crates/bevy_ui/src/widget/viewport.rs @@ -171,7 +171,7 @@ pub fn update_viewport_render_target_size( height: u32::max(1, size.y as u32), ..default() }; - let image = images.get_mut(image_handle).unwrap(); + let image = images.get_cloned_mut(image_handle).unwrap(); if image.data.is_some() { image.resize(size); } else { diff --git a/examples/shader/shader_material_wesl.rs b/examples/shader/shader_material_wesl.rs index 108093de78664..ea0416943f112 100644 --- a/examples/shader/shader_material_wesl.rs +++ b/examples/shader/shader_material_wesl.rs @@ -79,7 +79,7 @@ fn update( keys: Res>, ) { for (material, mut transform) in query.iter_mut() { - let material = materials.get_mut(material).unwrap(); + let material = materials.get_cloned_mut(material).unwrap(); material.time.x = time.elapsed_secs(); if keys.just_pressed(KeyCode::Space) { material.party_mode = !material.party_mode;