mirror of
https://github.com/bevyengine/bevy
synced 2025-01-04 17:28:56 +00:00
ed2b8e0f35
# Objective Add basic bubbling to observers, modeled off `bevy_eventlistener`. ## Solution - Introduce a new `Traversal` trait for components which point to other entities. - Provide a default `TraverseNone: Traversal` component which cannot be constructed. - Implement `Traversal` for `Parent`. - The `Event` trait now has an associated `Traversal` which defaults to `TraverseNone`. - Added a field `bubbling: &mut bool` to `Trigger` which can be used to instruct the runner to bubble the event to the entity specified by the event's traversal type. - Added an associated constant `SHOULD_BUBBLE` to `Event` which configures the default bubbling state. - Added logic to wire this all up correctly. Introducing the new associated information directly on `Event` (instead of a new `BubblingEvent` trait) lets us dispatch both bubbling and non-bubbling events through the same api. ## Testing I have added several unit tests to cover the common bugs I identified during development. Running the unit tests should be enough to validate correctness. The changes effect unsafe portions of the code, but should not change any of the safety assertions. ## Changelog Observers can now bubble up the entity hierarchy! To create a bubbling event, change your `Derive(Event)` to something like the following: ```rust #[derive(Component)] struct MyEvent; impl Event for MyEvent { type Traverse = Parent; // This event will propagate up from child to parent. const AUTO_PROPAGATE: bool = true; // This event will propagate by default. } ``` You can dispatch a bubbling event using the normal `world.trigger_targets(MyEvent, entity)`. Halting an event mid-bubble can be done using `trigger.propagate(false)`. Events with `AUTO_PROPAGATE = false` will not propagate by default, but you can enable it using `trigger.propagate(true)`. If there are multiple observers attached to a target, they will all be triggered by bubbling. They all share a bubbling state, which can be accessed mutably using `trigger.propagation_mut()` (`trigger.propagate` is just sugar for this). You can choose to implement `Traversal` for your own types, if you want to bubble along a different structure than provided by `bevy_hierarchy`. Implementers must be careful never to produce loops, because this will cause bevy to hang. ## Migration Guide + Manual implementations of `Event` should add associated type `Traverse = TraverseNone` and associated constant `AUTO_PROPAGATE = false`; + `Trigger::new` has new field `propagation: &mut Propagation` which provides the bubbling state. + `ObserverRunner` now takes the same `&mut Propagation` as a final parameter. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Torstein Grindvik <52322338+torsteingrindvik@users.noreply.github.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
43 lines
1.7 KiB
Rust
43 lines
1.7 KiB
Rust
//! A trait for components that let you traverse the ECS.
|
|
|
|
use crate::{
|
|
component::{Component, StorageType},
|
|
entity::Entity,
|
|
};
|
|
|
|
/// A component that can point to another entity, and which can be used to define a path through the ECS.
|
|
///
|
|
/// Traversals are used to [specify the direction] of [event propagation] in [observers]. By default,
|
|
/// events use the [`TraverseNone`] placeholder component, which cannot actually be created or added to
|
|
/// an entity and so never causes traversal.
|
|
///
|
|
/// Infinite loops are possible, and are not checked for. While looping can be desirable in some contexts
|
|
/// (for example, an observer that triggers itself multiple times before stopping), following an infinite
|
|
/// traversal loop without an eventual exit will can your application to hang. Each implementer of `Traversal`
|
|
/// for documenting possible looping behavior, and consumers of those implementations are responsible for
|
|
/// avoiding infinite loops in their code.
|
|
///
|
|
/// [specify the direction]: crate::event::Event::Traversal
|
|
/// [event propagation]: crate::observer::Trigger::propagate
|
|
/// [observers]: crate::observer::Observer
|
|
pub trait Traversal: Component {
|
|
/// Returns the next entity to visit.
|
|
fn traverse(&self) -> Option<Entity>;
|
|
}
|
|
|
|
/// A traversal component that doesn't traverse anything. Used to provide a default traversal
|
|
/// implementation for events.
|
|
///
|
|
/// It is not possible to actually construct an instance of this component.
|
|
pub enum TraverseNone {}
|
|
|
|
impl Traversal for TraverseNone {
|
|
#[inline(always)]
|
|
fn traverse(&self) -> Option<Entity> {
|
|
None
|
|
}
|
|
}
|
|
|
|
impl Component for TraverseNone {
|
|
const STORAGE_TYPE: StorageType = StorageType::Table;
|
|
}
|