use core::marker::PhantomData; use bevy_ecs::prelude::*; #[cfg(feature = "bevy_app")] use {crate::Parent, bevy_utils::HashSet, disqualified::ShortName}; /// When enabled, runs [`check_hierarchy_component_has_valid_parent`]. /// /// This resource is added by [`ValidParentCheckPlugin`]. /// 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 { /// Whether to run [`check_hierarchy_component_has_valid_parent`]. pub enabled: bool, _comp: PhantomData, } impl ReportHierarchyIssue { /// Constructs a new object pub fn new(enabled: bool) -> Self { ReportHierarchyIssue { enabled, _comp: Default::default(), } } } impl PartialEq for ReportHierarchyIssue { fn eq(&self, other: &Self) -> bool { self.enabled == other.enabled } } impl Default for ReportHierarchyIssue { fn default() -> Self { Self { enabled: cfg!(debug_assertions), _comp: PhantomData, } } } #[cfg(feature = "bevy_app")] /// 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 `InheritedVisibility` 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( parent_query: Query< (Entity, &Parent, Option<&bevy_core::Name>), (With, Or<(Changed, Added)>), >, component_query: Query<(), With>, mut already_diagnosed: Local>, ) { for (entity, parent, name) in &parent_query { let parent = parent.get(); if !component_query.contains(parent) && !already_diagnosed.contains(&entity) { already_diagnosed.insert(entity); bevy_utils::tracing::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 = ShortName::of::(), name = name.map_or_else(|| format!("Entity {}", entity), |s| format!("The {s} entity")), ); } } } /// Run criteria that only allows running when [`ReportHierarchyIssue`] is enabled. pub fn on_hierarchy_reports_enabled(report: Res>) -> bool where T: Component, { report.enabled } /// 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(PhantomData T>); impl Default for ValidParentCheckPlugin { fn default() -> Self { Self(PhantomData) } } #[cfg(feature = "bevy_app")] impl bevy_app::Plugin for ValidParentCheckPlugin { fn build(&self, app: &mut bevy_app::App) { app.init_resource::>().add_systems( bevy_app::Last, check_hierarchy_component_has_valid_parent:: .run_if(resource_equals(ReportHierarchyIssue::::new(true))), ); } }