Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_app/src/hierarchy.rs
9358 views
1
use core::marker::PhantomData;
2
3
use bevy_ecs::{
4
change_detection::MaybeLocation,
5
component::Component,
6
entity::Entity,
7
hierarchy::ChildOf,
8
intern::Interned,
9
lifecycle::Insert,
10
message::{Message, MessageReader, MessageWriter},
11
name::Name,
12
observer::On,
13
query::{With, Without},
14
schedule::{common_conditions::on_message, IntoScheduleConfigs, ScheduleLabel, SystemSet},
15
system::Query,
16
};
17
use bevy_platform::prelude::format;
18
use bevy_utils::prelude::DebugName;
19
use log::warn;
20
21
use crate::{Last, Plugin};
22
23
/// A plugin that verifies that [`Component`] `C` has parents that also have that component.
24
pub struct ValidateParentHasComponentPlugin<C: Component> {
25
schedule: Interned<dyn ScheduleLabel>,
26
marker: PhantomData<fn() -> C>,
27
}
28
29
impl<C: Component> Default for ValidateParentHasComponentPlugin<C> {
30
fn default() -> Self {
31
Self::in_schedule(Last)
32
}
33
}
34
35
impl<C: Component> ValidateParentHasComponentPlugin<C> {
36
/// Creates an instance of this plugin that inserts systems in the provided schedule.
37
pub fn in_schedule(label: impl ScheduleLabel) -> Self {
38
Self {
39
schedule: label.intern(),
40
marker: PhantomData,
41
}
42
}
43
}
44
45
impl<C: Component> Plugin for ValidateParentHasComponentPlugin<C> {
46
fn build(&self, app: &mut crate::App) {
47
app.add_message::<CheckParentHasComponent<C>>()
48
.add_observer(validate_parent_has_component::<C>)
49
.add_systems(
50
self.schedule,
51
check_parent_has_component::<C>
52
.run_if(on_message::<CheckParentHasComponent<C>>)
53
.in_set(ValidateParentHasComponentSystems),
54
);
55
}
56
}
57
58
/// System set for systems added by [`ValidateParentHasComponentPlugin`].
59
#[derive(SystemSet, PartialEq, Eq, Hash, Debug, Clone)]
60
pub struct ValidateParentHasComponentSystems;
61
62
/// An `Insert` observer that when run, will validate that the parent of a given entity contains
63
/// component `C`. If the parent does not contain `C`, a warning will be logged later in the frame.
64
fn validate_parent_has_component<C: Component>(
65
event: On<Insert, C>,
66
child: Query<&ChildOf>,
67
with_component: Query<(), With<C>>,
68
mut writer: MessageWriter<CheckParentHasComponent<C>>,
69
) {
70
let Ok(child_of) = child.get(event.entity) else {
71
return;
72
};
73
if with_component.contains(child_of.parent()) {
74
return;
75
}
76
// This entity may be configured incorrectly, or the parent may just not have been populated
77
// yet. Send a message to check again later.
78
writer.write(CheckParentHasComponent::<C> {
79
entity: event.entity,
80
caller: event.caller(),
81
marker: PhantomData,
82
});
83
}
84
85
/// A message to indicate that this entity should be checked if its parent has a component.
86
///
87
/// While we initially check when emitting these messages, we want to do a second check later on in
88
/// case the parent eventually gets populated.
89
#[derive(Message)]
90
struct CheckParentHasComponent<C: Component> {
91
/// The entity
92
entity: Entity,
93
caller: MaybeLocation,
94
marker: PhantomData<fn() -> C>,
95
}
96
97
/// System to handle "check parent" messages and log out any entities that still violate the
98
/// component hierarchy.
99
fn check_parent_has_component<C: Component>(
100
mut messages: MessageReader<CheckParentHasComponent<C>>,
101
children: Query<(&ChildOf, Option<&Name>), With<C>>,
102
components: Query<Option<&Name>, Without<C>>,
103
) {
104
for CheckParentHasComponent {
105
entity,
106
caller,
107
marker: _,
108
} in messages.read()
109
{
110
let Ok((child_of, name)) = children.get(*entity) else {
111
// Either the entity has been despawned, no longer has `C`, or is no longer a child. In
112
// any case, we can say that this situation is no longer relevant.
113
continue;
114
};
115
let parent = child_of.0;
116
let Ok(parent_name) = components.get(parent) else {
117
// This can only fail if the parent now has the `C` component. If the parent was
118
// despawned, the child entity would also be despawned.
119
continue;
120
};
121
let debug_name = DebugName::type_name::<C>();
122
warn!(
123
"warning[B0004]: {}{name} with the {ty_name} component has a parent ({parent_name}) without {ty_name}.\n\
124
This will cause inconsistent behaviors! See: https://bevy.org/learn/errors/b0004",
125
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
126
ty_name = debug_name.shortname(),
127
name = name.map_or_else(
128
|| format!("Entity {entity}"),
129
|s| format!("The {s} entity")
130
),
131
parent_name = parent_name.map_or_else(
132
|| format!("{parent} entity"),
133
|s| format!("the {s} entity")
134
),
135
);
136
}
137
}
138
139