bevy/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs
Nicola Papale 6c5403cf47 Add warning when a hierarchy component is missing (#5590)
# Objective

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.

- Fixes #5849
- Closes #5616
- Closes #2277 
- Closes #5081

## Solution

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 propagation tolerant to missing
  components (#5616)
- Probably archetype invariants, though the current draft would not
  allow detecting that kind of errors

---

## Changelog

- Add a warning when encountering dubious component hierarchy structure


Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
2022-09-19 16:12:11 +00:00

90 lines
3.2 KiB
Rust

use std::marker::PhantomData;
use bevy_app::{App, CoreStage, Plugin};
use bevy_core::Name;
use bevy_ecs::{prelude::*, schedule::ShouldRun};
use bevy_log::warn;
use bevy_utils::{get_short_name, HashSet};
use crate::Parent;
/// When enabled, runs [`check_hierarchy_component_has_valid_parent<T>`].
///
/// This resource is added by [`ValidParentCheckPlugin<T>`].
/// It is enabled on debug builds and disabled in release builds by default,
/// you can update this resource at runtime to change the default behavior.
#[derive(Resource)]
pub struct ReportHierarchyIssue<T> {
/// Whether to run [`check_hierarchy_component_has_valid_parent<T>`].
pub enabled: bool,
_comp: PhantomData<fn(T)>,
}
impl<T> Default for ReportHierarchyIssue<T> {
fn default() -> Self {
Self {
enabled: cfg!(debug_assertions),
_comp: PhantomData,
}
}
}
/// System to print a warning for each `Entity` with a `T` component
/// which parent hasn't a `T` component.
///
/// Hierarchy propagations are top-down, and limited only to entities
/// with a specific component (such as `ComputedVisibility` and `GlobalTransform`).
/// This means that entities with one of those component
/// and a parent without the same component is probably a programming error.
/// (See B0004 explanation linked in warning message)
pub fn check_hierarchy_component_has_valid_parent<T: Component>(
parent_query: Query<
(Entity, &Parent, Option<&Name>),
(With<T>, Or<(Changed<Parent>, Added<T>)>),
>,
component_query: Query<(), With<T>>,
mut already_diagnosed: Local<HashSet<Entity>>,
) {
for (entity, parent, name) in &parent_query {
let parent = parent.get();
if !component_query.contains(parent) && !already_diagnosed.contains(&entity) {
already_diagnosed.insert(entity);
warn!(
"warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\
This will cause inconsistent behaviors! See https://bevyengine.org/learn/errors/#B0004",
ty_name = get_short_name(std::any::type_name::<T>()),
name = name.map_or("An entity".to_owned(), |s| format!("The {s} entity")),
);
}
}
}
/// Run criteria that only allows running when [`ReportHierarchyIssue<T>`] is enabled.
pub fn on_hierarchy_reports_enabled<T>(report: Res<ReportHierarchyIssue<T>>) -> ShouldRun
where
T: Component,
{
report.enabled.into()
}
/// Print a warning for each `Entity` with a `T` component
/// whose parent doesn't have a `T` component.
///
/// See [`check_hierarchy_component_has_valid_parent`] for details.
pub struct ValidParentCheckPlugin<T: Component>(PhantomData<fn() -> T>);
impl<T: Component> Default for ValidParentCheckPlugin<T> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<T: Component> Plugin for ValidParentCheckPlugin<T> {
fn build(&self, app: &mut App) {
app.init_resource::<ReportHierarchyIssue<T>>()
.add_system_to_stage(
CoreStage::Last,
check_hierarchy_component_has_valid_parent::<T>
.with_run_criteria(on_hierarchy_reports_enabled::<T>),
);
}
}