Further improve docs for component hooks (#13475)

# Objective

While reviewing the other open hooks-related PRs, I found that the docs
on the `ComponentHooks` struct itself didn't give enough information
about how and why the feature could be used.

## Solution

1. Clean up the docs to add additional context.
2. Add a doc test demonstrating simple usage.

## Testing

The doc test passes locally.

---------

Co-authored-by: Alice Cecile <alice.i.cecil@gmail.com>
This commit is contained in:
Alice Cecile 2024-05-22 14:04:56 -04:00 committed by GitHub
parent 1ec5cdf3f2
commit dda7a744cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -182,7 +182,55 @@ pub enum StorageType {
/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`
pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId);
/// Lifecycle hooks for a given [`Component`], stored in its [`ComponentInfo`]
/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
///
/// Hooks are functions that run when a component is added, overwritten, or removed from an entity.
/// These are intended to be used for structural side effects that need to happen when a component is added or removed,
/// and are not intended for general-purpose logic.
///
/// For example, you might use a hook to update a cached index when a component is added,
/// to clean up resources when a component is removed,
/// or to keep hierarchical data structures across entities in sync.
///
/// This information is stored in the [`ComponentInfo`] of the associated component.
///
/// # Example
///
/// ```
/// use bevy_ecs::prelude::*;
/// use bevy_utils::HashSet;
///
/// #[derive(Component)]
/// struct MyTrackedComponent;
///
/// #[derive(Resource, Default)]
/// struct TrackedEntities(HashSet<Entity>);
///
/// let mut world = World::new();
/// world.init_resource::<TrackedEntities>();
///
/// // No entities with `MyTrackedComponent` have been added yet, so we can safely add component hooks
/// let mut tracked_component_query = world.query::<&MyTrackedComponent>();
/// assert!(tracked_component_query.iter(&world).next().is_none());
///
/// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, entity, _component_id| {
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
/// tracked_entities.0.insert(entity);
/// });
///
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, entity, _component_id| {
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
/// tracked_entities.0.remove(&entity);
/// });
///
/// let entity = world.spawn(MyTrackedComponent).id();
/// let tracked_entities = world.resource::<TrackedEntities>();
/// assert!(tracked_entities.0.contains(&entity));
///
/// world.despawn(entity);
/// let tracked_entities = world.resource::<TrackedEntities>();
/// assert!(!tracked_entities.0.contains(&entity));
/// ```
#[derive(Debug, Clone, Default)]
pub struct ComponentHooks {
pub(crate) on_add: Option<ComponentHook>,
@ -195,6 +243,8 @@ impl ComponentHooks {
/// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as
/// adding all of its components.
///
/// # Panics
///
/// Will panic if the component already has an `on_add` hook
pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_add(hook)
@ -202,9 +252,17 @@ impl ComponentHooks {
}
/// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
/// or replaced. The hook won't run if the component is already present and is only mutated.
/// or replaced.
///
/// An `on_insert` hook always runs after any `on_add` hooks (if the entity didn't already have the component).
///
/// # Warning
///
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
/// As a result, this is *not* an appropriate mechanism for reliably updating indexes and other caches.
///
/// # Panics
///
/// Will panic if the component already has an `on_insert` hook
pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_insert(hook)
@ -214,13 +272,18 @@ impl ComponentHooks {
/// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
/// Despawning an entity counts as removing all of its components.
///
/// # Panics
///
/// Will panic if the component already has an `on_remove` hook
pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_remove(hook)
.expect("Component id: {:?}, already has an on_remove hook")
}
/// Fallible version of [`Self::on_add`].
/// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity.
///
/// This is a fallible version of [`Self::on_add`].
///
/// Returns `None` if the component already has an `on_add` hook.
pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_add.is_some() {
@ -230,7 +293,10 @@ impl ComponentHooks {
Some(self)
}
/// Fallible version of [`Self::on_insert`].
/// Attempt to register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
///
/// This is a fallible version of [`Self::on_insert`].
///
/// Returns `None` if the component already has an `on_insert` hook.
pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_insert.is_some() {
@ -240,7 +306,10 @@ impl ComponentHooks {
Some(self)
}
/// Fallible version of [`Self::on_remove`].
/// Attempt to register a [`ComponentHook`] that will be run when this component is removed from an entity.
///
/// This is a fallible version of [`Self::on_remove`].
///
/// Returns `None` if the component already has an `on_remove` hook.
pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_remove.is_some() {