use core::marker::PhantomData;
use bevy_ecs::{
change_detection::MaybeLocation,
component::Component,
entity::Entity,
hierarchy::ChildOf,
intern::Interned,
lifecycle::Insert,
message::{Message, MessageReader, MessageWriter},
name::Name,
observer::On,
query::{With, Without},
schedule::{common_conditions::on_message, IntoScheduleConfigs, ScheduleLabel, SystemSet},
system::Query,
};
use bevy_platform::prelude::format;
use bevy_utils::prelude::DebugName;
use log::warn;
use crate::{Last, Plugin};
pub struct ValidateParentHasComponentPlugin<C: Component> {
schedule: Interned<dyn ScheduleLabel>,
marker: PhantomData<fn() -> C>,
}
impl<C: Component> Default for ValidateParentHasComponentPlugin<C> {
fn default() -> Self {
Self::in_schedule(Last)
}
}
impl<C: Component> ValidateParentHasComponentPlugin<C> {
pub fn in_schedule(label: impl ScheduleLabel) -> Self {
Self {
schedule: label.intern(),
marker: PhantomData,
}
}
}
impl<C: Component> Plugin for ValidateParentHasComponentPlugin<C> {
fn build(&self, app: &mut crate::App) {
app.add_message::<CheckParentHasComponent<C>>()
.add_observer(validate_parent_has_component::<C>)
.add_systems(
self.schedule,
check_parent_has_component::<C>
.run_if(on_message::<CheckParentHasComponent<C>>)
.in_set(ValidateParentHasComponentSystems),
);
}
}
#[derive(SystemSet, PartialEq, Eq, Hash, Debug, Clone)]
pub struct ValidateParentHasComponentSystems;
fn validate_parent_has_component<C: Component>(
event: On<Insert, C>,
child: Query<&ChildOf>,
with_component: Query<(), With<C>>,
mut writer: MessageWriter<CheckParentHasComponent<C>>,
) {
let Ok(child_of) = child.get(event.entity) else {
return;
};
if with_component.contains(child_of.parent()) {
return;
}
writer.write(CheckParentHasComponent::<C> {
entity: event.entity,
caller: event.caller(),
marker: PhantomData,
});
}
#[derive(Message)]
struct CheckParentHasComponent<C: Component> {
entity: Entity,
caller: MaybeLocation,
marker: PhantomData<fn() -> C>,
}
fn check_parent_has_component<C: Component>(
mut messages: MessageReader<CheckParentHasComponent<C>>,
children: Query<(&ChildOf, Option<&Name>), With<C>>,
components: Query<Option<&Name>, Without<C>>,
) {
for CheckParentHasComponent {
entity,
caller,
marker: _,
} in messages.read()
{
let Ok((child_of, name)) = children.get(*entity) else {
continue;
};
let parent = child_of.0;
let Ok(parent_name) = components.get(parent) else {
continue;
};
let debug_name = DebugName::type_name::<C>();
warn!(
"warning[B0004]: {}{name} with the {ty_name} component has a parent ({parent_name}) without {ty_name}.\n\
This will cause inconsistent behaviors! See: https://bevy.org/learn/errors/b0004",
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
ty_name = debug_name.shortname(),
name = name.map_or_else(
|| format!("Entity {entity}"),
|s| format!("The {s} entity")
),
parent_name = parent_name.map_or_else(
|| format!("{parent} entity"),
|s| format!("the {s} entity")
),
);
}
}