Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Documented Handles and Assets #3348

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions crates/bevy_asset/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Asset> {
Created { handle: Handle<T> },
Modified { handle: Handle<T> },
Expand Down Expand Up @@ -44,6 +46,18 @@ impl<T: Asset> Debug for AssetEvent<T> {
}

/// 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<T: Asset> {
assets: HashMap<HandleId, T>,
Expand All @@ -60,6 +74,10 @@ impl<T: Asset> Assets<T> {
}
}

/// Adds an asset to the collection, returning a Strong handle to that asset.
///
/// # Events
/// * [`AssetEvent::Created`]
pub fn add(&mut self, asset: T) -> Handle<T> {
let id = HandleId::random::<T>();
self.assets.insert(id, asset);
Expand All @@ -69,13 +87,27 @@ impl<T: Asset> Assets<T> {
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<H: Into<HandleId>>(&mut self, handle: H, asset: T) -> Handle<T> {
let id: HandleId = handle.into();
self.set_untracked(id, asset);
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
MrGVSV marked this conversation as resolved.
Show resolved Hide resolved
pub fn set_untracked<H: Into<HandleId>>(&mut self, handle: H, asset: T) {
let id: HandleId = handle.into();
if self.assets.insert(id, asset).is_some() {
Expand All @@ -89,14 +121,23 @@ impl<T: Asset> Assets<T> {
}
}

/// 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<H: Into<HandleId>>(&self, handle: H) -> Option<&T> {
self.assets.get(&handle.into())
}

/// Checks if an asset exists for the given handle
pub fn contains<H: Into<HandleId>>(&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<H: Into<HandleId>>(&mut self, handle: H) -> Option<&mut T> {
let id: HandleId = handle.into();
self.events.send(AssetEvent::Modified {
Expand All @@ -105,10 +146,15 @@ impl<T: Asset> Assets<T> {
self.assets.get_mut(&id)
}

/// Gets a _Strong_ handle pointing to the same asset as the given one
pub fn get_handle<H: Into<HandleId>>(&self, handle: H) -> Handle<T> {
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<H: Into<HandleId>>(
&mut self,
handle: H,
Expand All @@ -129,10 +175,12 @@ impl<T: Asset> Assets<T> {
borrowed
}

/// Get an iterator over all assets in the collection.
pub fn iter(&self) -> impl Iterator<Item = (HandleId, &T)> {
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<Item = (HandleId, &mut T)> {
for id in self.assets.keys() {
self.events.send(AssetEvent::Modified {
Expand All @@ -142,10 +190,17 @@ impl<T: Asset> Assets<T> {
self.assets.iter_mut().map(|(k, v)| (*k, v))
}

/// Get an iterator over all HandleIds in the collection.
pub fn ids(&self) -> impl Iterator<Item = HandleId> + '_ {
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<H: Into<HandleId>>(&mut self, handle: H) -> Option<T> {
let id: HandleId = handle.into();
let asset = self.assets.remove(&id);
Expand Down Expand Up @@ -190,10 +245,12 @@ impl<T: Asset> Assets<T> {
}
}

/// 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()
}
Expand Down
35 changes: 35 additions & 0 deletions crates/bevy_asset/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
MrGVSV marked this conversation as resolved.
Show resolved Hide resolved
/// 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.
MrGVSV marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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<T>
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,
Expand Down Expand Up @@ -123,6 +149,7 @@ impl<T: Asset> Handle<T> {
}
}

/// Get a copy of this handle as a Weak handle
pub fn as_weak<U: Asset>(&self) -> Handle<U> {
Handle {
id: self.id,
Expand All @@ -139,6 +166,9 @@ impl<T: Asset> Handle<T> {
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<T>) {
if self.is_strong() {
return;
Expand Down Expand Up @@ -266,6 +296,8 @@ impl<T: Asset> Clone for Handle<T> {
///
/// This allows handles to be mingled in a cross asset context. For example, storing `Handle<A>` and
/// `Handle<B>` in the same `HashSet<HandleUntyped>`.
///
/// To convert back to a typed handle, use the [typed](HandleUntyped::typed) method.
#[derive(Debug)]
pub struct HandleUntyped {
pub id: HandleId,
Expand Down Expand Up @@ -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<T: Asset>(mut self) -> Handle<T> {
if let HandleId::Id(type_uuid, _) = self.id {
if T::TYPE_UUID != type_uuid {
Expand Down