-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Description
Bevy version
0.16.0-rc.3
What you did
Bevy 0.15 added observer bubbling/ auto-propagation. This is useful for being able to scope observers to certain branches of a scene hierarchy. Here's an example that converts a non-bubbling event, Trigger<OnAdd>
into a bubbling one. The use-case is, spawning various scenes, and scoping observers to those scenes where you react to named components being added (eg, inserting marker components into a gltf hierarchy). Here's a working example from bevy 0.15:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, spawn_scene)
.run();
}
fn spawn_scene(mut commands: Commands, assets: Res<AssetServer>) {
commands.add_observer(bubble_up_onadd_name);
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 3.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
));
let scene: Handle<Scene> = assets.load("Torus.glb#Scene0");
commands
.spawn(SceneRoot(scene))
.observe(|trigger: Trigger<OnAddName>| {
println!("bubbled {}", trigger.0); // fires in bevy 0.15, but not in 0.16
});
}
#[derive(Component)]
struct OnAddName(String);
impl Event for OnAddName {
type Traversal = &'static Parent;
const AUTO_PROPAGATE: bool = true;
}
/// convert non-bubbling `OnAdd` into bubbling
fn bubble_up_onadd_name(
trigger: Trigger<OnAdd, Name>,
query: Query<&Name>,
mut commands: Commands,
) {
let Ok(name) = query.get(trigger.entity()) else { return };
commands.trigger_targets(OnAddName(name.to_string()), trigger.entity());
println!("triggered {}", name.to_string());
}
What went wrong
Here's the above code, converted to 0.16 syntax:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, spawn_scene)
.run();
}
fn spawn_scene(mut commands: Commands, assets: Res<AssetServer>) {
commands.add_observer(bubble_up_onadd_name);
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 3.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
));
let scene: Handle<Scene> = assets.load("Torus.glb#Scene0");
commands
.spawn(SceneRoot(scene))
.observe(|trigger: Trigger<OnAddName>| {
println!("bubbled {}", trigger.0); // fires in bevy 0.15, but not in 0.16
});
}
#[derive(Event)]
#[event(traversal = &'static ChildOf, auto_propagate)]
struct OnAddName(String);
fn bubble_up_onadd_name(
trigger: Trigger<OnAdd, Name>,
query: Query<&Name>,
mut commands: Commands,
) {
let Ok(name) = query.get(trigger.target()) else { return };
commands.trigger_targets(OnAddName(name.to_string()), trigger.target());
println!("triggered {}", name.to_string());
}
the event fails to bubble up, likely because the hierarchy isn't finalized yet.
Here's a workaround to achieve the desired functionality. Wait for SceneInstanceReady
, then recursively walk the hierarchy to find desired component (in this case Name
) and trigger the event. It feels like a bit of a downgrade in ergonomics though:
use bevy::{prelude::*, scene::SceneInstanceReady};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, spawn_scene)
.run();
}
fn spawn_scene(mut commands: Commands, assets: Res<AssetServer>) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 3.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
));
let scene: Handle<Scene> = assets.load("Torus.glb#Scene0");
commands
.spawn(SceneRoot(scene))
.observe(
|trigger: Trigger<SceneInstanceReady>,
query: Query<(Option<&Name>, Option<&Children>)>,
commands: Commands| {
recursively_seek_name(trigger.target(), query, commands);
},
)
.observe(|trigger: Trigger<OnAddName>| {
println!("bubbled {}", trigger.0);
});
}
fn recursively_seek_name(
entity: Entity,
query: Query<(Option<&Name>, Option<&Children>)>,
mut commands: Commands,
) {
let (maybe_name, maybe_children) = query.get(entity).unwrap();
if let Some(name) = maybe_name {
commands.trigger_targets(OnAddName(name.to_string()), entity);
}
if let Some(children) = maybe_children {
for child in children {
recursively_seek_name(*child, query, commands.reborrow());
}
}
}
#[derive(Event)]
#[event(traversal = &'static ChildOf, auto_propagate)]
struct OnAddName(String);
TLDR
OnAdd
firing before the hierarchy (and possibly other relationship types) have been finalized reduces the usefulness of relationship traversing features such as auto-propagating events