Skip to content

Commit b021e81

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 1152111 commit b021e81

File tree

6 files changed

+160
-3
lines changed

6 files changed

+160
-3
lines changed

crates/bevy_hierarchy/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ trace = []
1515
# bevy
1616
bevy_app = { path = "../bevy_app", version = "0.9.0-dev" }
1717
bevy_ecs = { path = "../bevy_ecs", version = "0.9.0-dev", features = ["bevy_reflect"] }
18+
bevy_log = { path = "../bevy_log", version = "0.9.0-dev" }
1819
bevy_reflect = { path = "../bevy_reflect", version = "0.9.0-dev", features = ["bevy"] }
1920
bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" }
2021

crates/bevy_hierarchy/src/hierarchy.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use crate::components::{Children, Parent};
22
use bevy_ecs::{
3+
component::Component,
34
entity::Entity,
4-
system::{Command, EntityCommands},
5+
query::{Added, Changed, Or, With},
6+
system::{Command, EntityCommands, Local, Query},
57
world::{EntityMut, World},
68
};
7-
use bevy_utils::tracing::debug;
9+
use bevy_log::warn;
10+
use bevy_utils::{get_short_name, tracing::debug};
811

912
/// Despawns the given entity and all its children recursively
1013
#[derive(Debug)]
@@ -20,6 +23,29 @@ pub struct DespawnChildrenRecursive {
2023
pub entity: Entity,
2124
}
2225

26+
/// System to print a warning if at any time we detect an inconsistent hierarchy.
27+
pub fn hierarchy_healthcheck_system<T: Component>(
28+
parent_query: Query<&Parent, (With<T>, Or<(Changed<Parent>, Added<T>)>)>,
29+
component_query: Query<(), With<T>>,
30+
mut already_diagnosed: Local<bool>,
31+
) {
32+
if *already_diagnosed {
33+
return;
34+
}
35+
for parent_of_component in &parent_query {
36+
let parent = parent_of_component.get();
37+
if !component_query.contains(parent) {
38+
*already_diagnosed = true;
39+
warn!(
40+
"warning[B0004]: An entity with the {ty_name} component has a parent without {ty_name}.\n\
41+
This will cause inconsistent behaviors! See https://bevyengine.org/learn/errors/#B0004",
42+
ty_name=get_short_name(std::any::type_name::<T>()),
43+
);
44+
return;
45+
}
46+
}
47+
}
48+
2349
/// Function for despawning an entity and all its children
2450
pub fn despawn_with_children_recursive(world: &mut World, entity: Entity) {
2551
// first, make the entity's own parent forget about it

crates/bevy_render/src/view/visibility/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub use render_layers::*;
55
use bevy_app::{CoreStage, Plugin};
66
use bevy_asset::{Assets, Handle};
77
use bevy_ecs::prelude::*;
8-
use bevy_hierarchy::{Children, Parent};
8+
use bevy_hierarchy::{hierarchy_healthcheck_system, Children, Parent};
99
use bevy_reflect::std_traits::ReflectDefault;
1010
use bevy_reflect::Reflect;
1111
use bevy_transform::components::GlobalTransform;
@@ -170,6 +170,9 @@ pub enum VisibilitySystems {
170170
/// Label for the [`check_visibility()`] system updating each frame the [`ComputedVisibility`]
171171
/// of each entity and the [`VisibleEntities`] of each view.
172172
CheckVisibility,
173+
/// Check if [`ComputedVisibility`]
174+
/// entities' parents also have a [`ComputedVisibility`] component.
175+
HealthCheck,
173176
}
174177

175178
pub struct VisibilityPlugin;
@@ -214,6 +217,10 @@ impl Plugin for VisibilityPlugin {
214217
.after(UpdateProjectionFrusta)
215218
.after(VisibilityPropagate)
216219
.after(TransformSystem::TransformPropagate),
220+
)
221+
.add_system_to_stage(
222+
CoreStage::Last,
223+
hierarchy_healthcheck_system::<ComputedVisibility>.label(HealthCheck),
217224
);
218225
}
219226
}

crates/bevy_transform/src/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod prelude {
1414

1515
use bevy_app::prelude::*;
1616
use bevy_ecs::prelude::*;
17+
use bevy_hierarchy::hierarchy_healthcheck_system;
1718
use prelude::{GlobalTransform, Transform};
1819

1920
/// A [`Bundle`] of the [`Transform`] and [`GlobalTransform`]
@@ -81,6 +82,9 @@ impl From<Transform> for TransformBundle {
8182
pub enum TransformSystem {
8283
/// Propagates changes in transform to children's [`GlobalTransform`](crate::components::GlobalTransform)
8384
TransformPropagate,
85+
/// Check if [`GlobalTransform`](crate::components::GlobalTransform)
86+
/// entities' parents also have a `GlobalTransform` component.
87+
HealthCheck,
8488
}
8589

8690
/// The base plugin for handling [`Transform`] components
@@ -99,6 +103,10 @@ impl Plugin for TransformPlugin {
99103
.add_system_to_stage(
100104
CoreStage::PostUpdate,
101105
systems::transform_propagate_system.label(TransformSystem::TransformPropagate),
106+
)
107+
.add_system_to_stage(
108+
CoreStage::Last,
109+
hierarchy_healthcheck_system::<GlobalTransform>.label(TransformSystem::HealthCheck),
102110
);
103111
}
104112
}

errors/B0004.md

+112
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

+3
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)