bevy_hierarchy/
valid_parent_check_plugin.rs1use std::marker::PhantomData;
2
3#[cfg(feature = "bevy_app")]
4use crate::Parent;
5use bevy_ecs::prelude::*;
6#[cfg(feature = "bevy_app")]
7use bevy_utils::{get_short_name, HashSet};
8
9#[derive(Resource)]
15pub struct ReportHierarchyIssue<T> {
16 pub enabled: bool,
18 _comp: PhantomData<fn(T)>,
19}
20
21impl<T> ReportHierarchyIssue<T> {
22 pub fn new(enabled: bool) -> Self {
24 ReportHierarchyIssue {
25 enabled,
26 _comp: Default::default(),
27 }
28 }
29}
30
31impl<T> PartialEq for ReportHierarchyIssue<T> {
32 fn eq(&self, other: &Self) -> bool {
33 self.enabled == other.enabled
34 }
35}
36
37impl<T> Default for ReportHierarchyIssue<T> {
38 fn default() -> Self {
39 Self {
40 enabled: cfg!(debug_assertions),
41 _comp: PhantomData,
42 }
43 }
44}
45
46#[cfg(feature = "bevy_app")]
47pub fn check_hierarchy_component_has_valid_parent<T: Component>(
56 parent_query: Query<
57 (Entity, &Parent, Option<&bevy_core::Name>),
58 (With<T>, Or<(Changed<Parent>, Added<T>)>),
59 >,
60 component_query: Query<(), With<T>>,
61 mut already_diagnosed: Local<HashSet<Entity>>,
62) {
63 for (entity, parent, name) in &parent_query {
64 let parent = parent.get();
65 if !component_query.contains(parent) && !already_diagnosed.contains(&entity) {
66 already_diagnosed.insert(entity);
67 bevy_utils::tracing::warn!(
68 "warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\
69 This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/#b0004",
70 ty_name = get_short_name(std::any::type_name::<T>()),
71 name = name.map_or_else(|| format!("Entity {}", entity), |s| format!("The {s} entity")),
72 );
73 }
74 }
75}
76
77pub fn on_hierarchy_reports_enabled<T>(report: Res<ReportHierarchyIssue<T>>) -> bool
79where
80 T: Component,
81{
82 report.enabled
83}
84
85pub struct ValidParentCheckPlugin<T: Component>(PhantomData<fn() -> T>);
90impl<T: Component> Default for ValidParentCheckPlugin<T> {
91 fn default() -> Self {
92 Self(PhantomData)
93 }
94}
95
96#[cfg(feature = "bevy_app")]
97impl<T: Component> bevy_app::Plugin for ValidParentCheckPlugin<T> {
98 fn build(&self, app: &mut bevy_app::App) {
99 app.init_resource::<ReportHierarchyIssue<T>>().add_systems(
100 bevy_app::Last,
101 check_hierarchy_component_has_valid_parent::<T>
102 .run_if(resource_equals(ReportHierarchyIssue::<T>::new(true))),
103 );
104 }
105}