bevyengine / bevyengine/bevy

`OnAdd` events fire before hierarchy has been finalized in 0.16.0-rc.3

Open
#18,671 2 comments 3 reactions 0 assignees View on GitHub
A-ECS A-Scenes C-Bug S-Needs-Design
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

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` 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:

```rust
use bevy::prelude::*;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, spawn_scene)
.run();
}

fn spawn_scene(mut commands: Commands, assets: Res) {
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 = assets.load("Torus.glb#Scene0");
commands
.spawn(SceneRoot(scene))
.observe(|trigger: Trigger| {
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,
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:

```rust
use bevy::prelude::*;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, spawn_scene)
.run();
}

fn spawn_scene(mut commands: Commands, assets: Res) {
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 = assets.load("Torus.glb#Scene0");
commands
.spawn(SceneRoot(scene))
.observe(|trigger: Trigger| {
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,
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:

```rust
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) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 3.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
));
let scene: Handle = assets.load("Torus.glb#Scene0");
commands
.spawn(SceneRoot(scene))
.observe(
|trigger: Trigger,
query: Query<(Option<&Name>, Option<&Children>)>,
commands: Commands| {
recursively_seek_name(trigger.target(), query, commands);
},
)
.observe(|trigger: Trigger| {
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

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.