diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index d92e535f80..8d248ff911 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -9,6 +9,8 @@ use crossbeam_channel::Sender; use std::fmt::Debug; /// Events that happen on assets of type `T` +/// +/// Events sent via the [Assets] struct will always be sent with a _Weak_ handle pub enum AssetEvent { Created { handle: Handle }, Modified { handle: Handle }, @@ -44,6 +46,18 @@ impl Debug for AssetEvent { } /// Stores Assets of a given type and tracks changes to them. +/// +/// Each asset is mapped by a unique [HandleId](crate::HandleId), allowing any [Handle](crate::Handle) +/// with the same HandleId to access it. These assets remain loaded for as long as a Strong handle +/// to that asset exists. +/// +/// To store a reference to an asset without forcing it to stay loaded, you can use a Weak handle. +/// To make a Weak handle a Strong one, use [Assets::get_handle](crate::Assets::get_handle) or pass +/// the Assets collection into the handle's [make_strong](crate::Handle::make_strong) method. +/// +/// Remember, if there are no Strong handles for an asset (i.e. they have all been dropped), the asset +/// will unload. Make sure you always have a Strong handle when you want to keep an asset loaded! +/// #[derive(Debug)] pub struct Assets { assets: HashMap, @@ -60,6 +74,10 @@ impl Assets { } } + /// Adds an asset to the collection, returning a Strong handle to that asset. + /// + /// # Events + /// * [`AssetEvent::Created`] pub fn add(&mut self, asset: T) -> Handle { let id = HandleId::random::(); self.assets.insert(id, asset); @@ -69,6 +87,12 @@ impl Assets { self.get_handle(id) } + /// Add/modify the asset pointed to by the given handle. + /// + /// Unless there exists another Strong handle for this asset, it's advised to use the returned + /// Strong handle. Not doing so may result in the unexpected release of the asset. + /// + /// See [set_untracked](Assets::set_untracked) for more info. #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] pub fn set>(&mut self, handle: H, asset: T) -> Handle { let id: HandleId = handle.into(); @@ -76,6 +100,14 @@ impl Assets { self.get_handle(id) } + /// Add/modify the asset pointed to by the given handle. + /// + /// If an asset already exists with the given HandleId, it will be modified. Otherwise the new asset + /// will be inserted. + /// + /// # Events + /// * [`AssetEvent::Created`]: Sent if the asset did not yet exist with the given handle + /// * [`AssetEvent::Modified`]: Sent if the asset with given handle already existed pub fn set_untracked>(&mut self, handle: H, asset: T) { let id: HandleId = handle.into(); if self.assets.insert(id, asset).is_some() { @@ -89,14 +121,23 @@ impl Assets { } } + /// Get the asset for the given handle. + /// + /// This is the main method for accessing asset data from an [Assets] collection. If you need + /// mutable access to the asset, use [get_mut](Assets::get_mut). pub fn get>(&self, handle: H) -> Option<&T> { self.assets.get(&handle.into()) } + /// Checks if an asset exists for the given handle pub fn contains>(&self, handle: H) -> bool { self.assets.contains_key(&handle.into()) } + /// Get mutable access to the asset for the given handle. + /// + /// This is the main method for mutably accessing asset data from an [Assets] collection. If you + /// do not need mutable access to the asset, you may also use [get](Assets::get). pub fn get_mut>(&mut self, handle: H) -> Option<&mut T> { let id: HandleId = handle.into(); self.events.send(AssetEvent::Modified { @@ -105,10 +146,15 @@ impl Assets { self.assets.get_mut(&id) } + /// Gets a _Strong_ handle pointing to the same asset as the given one pub fn get_handle>(&self, handle: H) -> Handle { Handle::strong(handle.into(), self.ref_change_sender.clone()) } + /// Get mutable access to an asset for the given handle, inserting a new value if none exists. + /// + /// # Events + /// * [`AssetEvent::Created`]: Sent if the asset did not yet exist with the given handle pub fn get_or_insert_with>( &mut self, handle: H, @@ -129,10 +175,12 @@ impl Assets { borrowed } + /// Get an iterator over all assets in the collection. pub fn iter(&self) -> impl Iterator { self.assets.iter().map(|(k, v)| (*k, v)) } + /// Get a mutable iterator over all assets in the collection. pub fn iter_mut(&mut self) -> impl Iterator { for id in self.assets.keys() { self.events.send(AssetEvent::Modified { @@ -142,10 +190,17 @@ impl Assets { self.assets.iter_mut().map(|(k, v)| (*k, v)) } + /// Get an iterator over all HandleIds in the collection. pub fn ids(&self) -> impl Iterator + '_ { self.assets.keys().cloned() } + /// Remove an asset for the given handle. + /// + /// The asset is returned if it existed in the collection, otherwise `None`. + /// + /// # Events + /// * [`AssetEvent::Removed`] pub fn remove>(&mut self, handle: H) -> Option { let id: HandleId = handle.into(); let asset = self.assets.remove(&id); @@ -190,10 +245,12 @@ impl Assets { } } + /// Gets the number of assets in the collection pub fn len(&self) -> usize { self.assets.len() } + /// Returns true if there are no stored assets pub fn is_empty(&self) -> bool { self.assets.is_empty() } diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index bdbba2a25f..d5287a2f17 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -69,12 +69,38 @@ impl HandleId { /// /// Handles contain a unique id that corresponds to a specific asset in the [Assets](crate::Assets) /// collection. +/// +/// # Accessing the Asset +/// +/// A handle is _not_ the asset itself, but should be seen as a pointer to the asset. Modifying a +/// handle's `id` only modifies which asset is being pointed to. To get the actual asset, try using +/// [`Assets::get`](crate::Assets::get) or [`Assets::get_mut`](crate::Assets::get_mut). +/// +/// # Strong and Weak +/// +/// A handle can be either "Strong" or "Weak". Simply put: Strong handles keep the asset loaded, +/// while Weak handles do not affect the loaded status of assets. This is due to a type of +/// _reference counting_. When the number of Strong handles that exist for any given asset reach +/// zero, the asset is dropped and becomes unloaded. In some cases, you might want a reference to an +/// asset but don't want to take the responsibility of keeping it loaded that comes with a Strong handle. +/// This is where a Weak handle can be very useful. +/// +/// For example, imagine you have a `Sprite` component and a `Collider` component. The `Collider` uses +/// the `Sprite`'s image size to check for collisions. It does so by keeping a Weak copy of the +/// `Sprite`'s Strong handle to the image asset. +/// +/// If the `Sprite` is removed, its Strong handle to the image is dropped with it. And since it was the +/// only Strong handle for that asset, the asset is unloaded. Our `Collider` component still has a Weak +/// handle to the unloaded asset, but it will not be able to retrieve the image data, resulting in +/// collisions no longer being detected for that entity. +/// #[derive(Component, Reflect, FromReflect)] #[reflect(Component)] pub struct Handle where T: Asset, { + /// The ID of the asset as contained within its respective [Assets](crate::Assets) collection pub id: HandleId, #[reflect(ignore)] handle_type: HandleType, @@ -123,6 +149,7 @@ impl Handle { } } + /// Get a copy of this handle as a Weak handle pub fn as_weak(&self) -> Handle { Handle { id: self.id, @@ -139,6 +166,9 @@ impl Handle { matches!(self.handle_type, HandleType::Strong(_)) } + /// Makes this handle Strong if it wasn't already. + /// + /// This method requires the corresponding [Assets](crate::Assets) collection pub fn make_strong(&mut self, assets: &mut Assets) { if self.is_strong() { return; @@ -266,6 +296,8 @@ impl Clone for Handle { /// /// This allows handles to be mingled in a cross asset context. For example, storing `Handle` and /// `Handle` in the same `HashSet`. +/// +/// To convert back to a typed handle, use the [typed](HandleUntyped::typed) method. #[derive(Debug)] pub struct HandleUntyped { pub id: HandleId, @@ -307,6 +339,9 @@ impl HandleUntyped { matches!(self.handle_type, HandleType::Strong(_)) } + /// Convert this handle into a typed [Handle]. + /// + /// The new handle will maintain the Strong or Weak status of the current handle. pub fn typed(mut self) -> Handle { if let HandleId::Id(type_uuid, _) = self.id { if T::TYPE_UUID != type_uuid {