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