Skip to content

Commit 26b8f08

Browse files
committed
Add warning when a hierarchy component is missing
A common pitfall since 0.8 is the requirement on `ComputedVisibility` being present on all ancestors of an entity that itself has `ComputedVisibility`, without which, the entity becomes invisible. I myself hit the issue and got very confused, and saw a few people hit it as well, so it makes sense to provide a hint of what to do when such a situation is encountered. We now check that all entities with both a `Parent` and a `ComputedVisibility` component have parents that themselves have a `ComputedVisibility` component. Note that the warning is only printed once. We also add a similar warning to `GlobalTransform`. This only emits a warning. Because sometimes it could be an intended behavior. Alternatives: - Do nothing and keep repeating to newcomers how to avoid recurring pitfalls - Make the transform and visibility propagations tolerant to missing components
1 parent 56fc1df commit 26b8f08

File tree

6 files changed

+192
-1
lines changed

6 files changed

+192
-1
lines changed

crates/bevy_hierarchy/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ trace = []
1414
[dependencies]
1515
# bevy
1616
bevy_app = { path = "../bevy_app", version = "0.9.0-dev" }
17+
bevy_core = { path = "../bevy_core", version = "0.9.0-dev" }
1718
bevy_ecs = { path = "../bevy_ecs", version = "0.9.0-dev", features = ["bevy_reflect"] }
19+
bevy_log = { path = "../bevy_log", version = "0.9.0-dev" }
1820
bevy_reflect = { path = "../bevy_reflect", version = "0.9.0-dev", features = ["bevy"] }
1921
bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" }
2022

crates/bevy_hierarchy/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@ pub use child_builder::*;
1616
mod events;
1717
pub use events::*;
1818

19+
mod valid_parent_check_plugin;
20+
pub use valid_parent_check_plugin::*;
21+
1922
#[doc(hidden)]
2023
pub mod prelude {
2124
#[doc(hidden)]
22-
pub use crate::{child_builder::*, components::*, hierarchy::*, HierarchyPlugin};
25+
pub use crate::{
26+
child_builder::*, components::*, hierarchy::*, HierarchyPlugin, ValidParentCheckPlugin,
27+
};
2328
}
2429

2530
use bevy_app::prelude::*;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use std::marker::PhantomData;
2+
3+
use bevy_app::{App, CoreStage, Plugin};
4+
use bevy_core::Name;
5+
use bevy_ecs::prelude::*;
6+
use bevy_log::warn;
7+
use bevy_utils::{get_short_name, HashSet};
8+
9+
use crate::Parent;
10+
11+
/// System to print a warning for each `Entity` with a `T` component
12+
/// which parent hasn't a `T` component.
13+
///
14+
/// Hierarchy propagations are top-down, and limited only to entities
15+
/// with a specific component (such as `ComputedVisibility` and `GlobalTransform`).
16+
/// This means that entities with one of those component
17+
/// and a parent without the same component is probably a programming error.
18+
/// (See B0004 explanation linked in warning message)
19+
pub fn check_hierarchy_component_has_valid_parent<T: Component>(
20+
parent_query: Query<
21+
(Entity, &Parent, Option<&Name>),
22+
(With<T>, Or<(Changed<Parent>, Added<T>)>),
23+
>,
24+
component_query: Query<(), With<T>>,
25+
mut already_diagnosed: Local<HashSet<Entity>>,
26+
) {
27+
for (entity, parent, name) in &parent_query {
28+
let parent = parent.get();
29+
if !component_query.contains(parent) && !already_diagnosed.contains(&entity) {
30+
already_diagnosed.insert(entity);
31+
warn!(
32+
"warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\
33+
This will cause inconsistent behaviors! See https://bevyengine.org/learn/errors/#B0004",
34+
ty_name = get_short_name(std::any::type_name::<T>()),
35+
name = name.map_or("An entity".to_owned(), |s| format!("The {s} entity")),
36+
);
37+
}
38+
}
39+
}
40+
41+
/// Print a warning for each `Entity` with a `T` component
42+
/// which parent hasn't a `T` component.
43+
///
44+
/// See [`check_hierarchy_component_has_valid_parent`] for details.
45+
pub struct ValidParentCheckPlugin<T>(PhantomData<fn() -> T>);
46+
impl<T: Component> Default for ValidParentCheckPlugin<T> {
47+
fn default() -> Self {
48+
Self(PhantomData)
49+
}
50+
}
51+
52+
impl<T: Component> Plugin for ValidParentCheckPlugin<T> {
53+
fn build(&self, app: &mut App) {
54+
app.add_system_to_stage(
55+
CoreStage::Last,
56+
check_hierarchy_component_has_valid_parent::<T>,
57+
);
58+
}
59+
}

crates/bevy_internal/src/default_plugins.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ impl PluginGroup for DefaultPlugins {
3535
group.add(bevy_input::InputPlugin::default());
3636
group.add(bevy_window::WindowPlugin::default());
3737

38+
#[cfg(debug_assertions)]
39+
group.add(bevy_hierarchy::ValidParentCheckPlugin::<
40+
crate::prelude::GlobalTransform,
41+
>::default());
42+
3843
#[cfg(feature = "bevy_asset")]
3944
group.add(bevy_asset::AssetPlugin::default());
4045

@@ -50,6 +55,11 @@ impl PluginGroup for DefaultPlugins {
5055
#[cfg(feature = "bevy_render")]
5156
group.add(bevy_render::RenderPlugin::default());
5257

58+
#[cfg(all(feature = "bevy_render", debug_assertions))]
59+
group.add(bevy_hierarchy::ValidParentCheckPlugin::<
60+
crate::prelude::ComputedVisibility,
61+
>::default());
62+
5363
#[cfg(feature = "bevy_core_pipeline")]
5464
group.add(bevy_core_pipeline::CorePipelinePlugin::default());
5565

errors/B0004.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# B0004
2+
3+
A runtime warning.
4+
5+
An [`Entity`] with a hierarchy-inherited component has a [`Parent`]
6+
without the hierarchy-inherited component in question.
7+
8+
The hierarchy-inherited components are:
9+
10+
- [`ComputedVisibility`]
11+
- [`GlobalTransform`]
12+
13+
For example, the following code will cause a warning to be emitted:
14+
15+
```rust,no_run
16+
use bevy::prelude::*;
17+
18+
// WARNING: this code is buggy
19+
fn setup_cube(
20+
mut commands: Commands,
21+
mut meshes: ResMut<Assets<Mesh>>,
22+
mut materials: ResMut<Assets<StandardMaterial>>,
23+
) {
24+
commands
25+
.spawn_bundle(TransformBundle::default())
26+
.with_children(|parent| {
27+
// cube
28+
parent.spawn_bundle(PbrBundle {
29+
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
30+
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
31+
transform: Transform::from_xyz(0.0, 0.5, 0.0),
32+
..default()
33+
});
34+
});
35+
36+
// camera
37+
commands.spawn_bundle(Camera3dBundle {
38+
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
39+
..default()
40+
});
41+
}
42+
43+
fn main() {
44+
App::new()
45+
.add_plugins(DefaultPlugins)
46+
.add_startup_system(setup_cube)
47+
.run();
48+
}
49+
```
50+
51+
This code **will not** show a cube on screen.
52+
This is because the entity spawned with `commands.spawn_bundle(…)`
53+
doesn't have a [`ComputedVisibility`] component.
54+
Since the cube is spawned as a child of an entity without the
55+
[`ComputedVisibility`] component, it will not be visible at all.
56+
57+
To fix this, you must use [`SpatialBundle`] over [`TransformBundle`],
58+
as follow:
59+
60+
```rust,no_run
61+
use bevy::prelude::*;
62+
63+
fn setup_cube(
64+
mut commands: Commands,
65+
mut meshes: ResMut<Assets<Mesh>>,
66+
mut materials: ResMut<Assets<StandardMaterial>>,
67+
) {
68+
commands
69+
// We use SpatialBundle instead of TransformBundle, it contains the
70+
// ComputedVisibility component needed to display the cube,
71+
// In addition to the Transform and GlobalTransform components.
72+
.spawn_bundle(SpatialBundle::default())
73+
.with_children(|parent| {
74+
// cube
75+
parent.spawn_bundle(PbrBundle {
76+
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
77+
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
78+
transform: Transform::from_xyz(0.0, 0.5, 0.0),
79+
..default()
80+
});
81+
});
82+
83+
// camera
84+
commands.spawn_bundle(Camera3dBundle {
85+
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
86+
..default()
87+
});
88+
}
89+
90+
fn main() {
91+
App::new()
92+
.add_plugins(DefaultPlugins)
93+
.add_startup_system(setup_cube)
94+
.run();
95+
}
96+
```
97+
98+
A similar problem occurs when the [`GlobalTransform`] component is missing.
99+
However, when a parent [`GlobalTransform`] is missing,
100+
it will simply prevent all transform propagation,
101+
including when updating the [`Transform`] component of the child.
102+
103+
You will most likely encouter this warning when loading a scene
104+
as a child of a pre-existing [`Entity`] that does not have the proper components.
105+
106+
[`ComputedVisibility`]: https://docs.rs/bevy/*/bevy/render/view/struct.ComputedVisibility.html
107+
[`GlobalTransform`]: https://docs.rs/bevy/*/bevy/transform/components/struct.GlobalTransform.html
108+
[`Transform`]: https://docs.rs/bevy/*/bevy/transform/components/struct.Transform.html
109+
[`Parent`]: https://docs.rs/bevy/*/bevy/hierarchy/struct.Parent.html
110+
[`Entity`]: https://docs.rs/bevy/*/bevy/ecs/entity/struct.Entity.html
111+
[`SpatialBundle`]: https://docs.rs/bevy/*/bevy/render/prelude/struct.SpatialBundle.html
112+
[`TransformBundle`]: https://docs.rs/bevy/*/bevy/transform/struct.TransformBundle.html

errors/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ pub struct B0002;
66

77
#[doc = include_str!("../B0003.md")]
88
pub struct B0003;
9+
10+
#[doc = include_str!("../B0004.md")]
11+
pub struct B0004;

0 commit comments

Comments
 (0)