use std::marker::PhantomData;
use bevy::{
ecs::{intern::Interned, schedule::ScheduleLabel},
hierarchy::HierarchyEvent,
prelude::*,
};
pub struct AncestorMarkerPlugin<C: Component> {
schedule: Interned<dyn ScheduleLabel>,
system_set: Option<Interned<dyn SystemSet>>,
_phantom: PhantomData<C>,
}
impl<C: Component> AncestorMarkerPlugin<C> {
pub fn new(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
system_set: None,
_phantom: PhantomData,
}
}
pub fn add_markers_in_set(mut self, set: impl SystemSet) -> Self {
self.system_set = Some(set.intern());
self
}
}
impl<C: Component> Plugin for AncestorMarkerPlugin<C> {
fn build(&self, app: &mut App) {
app.observe(
|trigger: Trigger<OnAdd, C>,
mut commands: Commands,
parent_query: Query<&Parent>,
ancestor_query: Query<(), With<AncestorMarker<C>>>| {
let entity = trigger.entity();
if parent_query.contains(entity) {
add_ancestor_markers(
entity,
&mut commands,
&parent_query,
&ancestor_query,
false,
);
}
},
);
#[allow(clippy::type_complexity)]
app.observe(
|trigger: Trigger<OnRemove, C>,
mut commands: Commands,
child_query: Query<&Children>,
parent_query: Query<&Parent>,
ancestor_query: Query<
(Entity, Has<C>),
Or<(With<AncestorMarker<C>>, With<C>)>
>| {
remove_ancestor_markers(trigger.entity(), &mut commands, &parent_query, &child_query, &ancestor_query, false);
},
);
app.add_event::<HierarchyEvent>();
if let Some(set) = self.system_set {
app.add_systems(
self.schedule,
update_markers_on_hierarchy_changes::<C>.in_set(set),
);
} else {
app.add_systems(self.schedule, update_markers_on_hierarchy_changes::<C>);
}
}
}
#[derive(Component, Copy, Reflect)]
#[reflect(Component, Default)]
pub struct AncestorMarker<C: Component> {
#[reflect(ignore)]
_phantom: PhantomData<C>,
}
impl<C: Component> Clone for AncestorMarker<C> {
fn clone(&self) -> Self {
Self::default()
}
}
impl<C: Component> Default for AncestorMarker<C> {
fn default() -> Self {
Self {
_phantom: PhantomData,
}
}
}
#[allow(clippy::type_complexity)]
fn update_markers_on_hierarchy_changes<C: Component>(
mut commands: Commands,
entity_query: Query<(), With<C>>,
parent_query: Query<&Parent>,
child_query: Query<&Children>,
ancestor_query_1: Query<(), With<AncestorMarker<C>>>,
ancestor_query_2: Query<(Entity, Has<C>), Or<(With<AncestorMarker<C>>, With<C>)>>,
mut hierarchy_event: EventReader<HierarchyEvent>,
) {
for event in hierarchy_event.read().cloned() {
match event {
HierarchyEvent::ChildAdded { child, parent } => {
if entity_query.contains(child) {
add_ancestor_markers(
parent,
&mut commands,
&parent_query,
&ancestor_query_1,
true,
);
}
}
HierarchyEvent::ChildRemoved { child, parent } => {
if entity_query.contains(child) {
remove_ancestor_markers(
parent,
&mut commands,
&parent_query,
&child_query,
&ancestor_query_2,
true,
);
}
}
HierarchyEvent::ChildMoved {
child,
previous_parent,
new_parent,
} => {
if entity_query.contains(child) {
remove_ancestor_markers(
previous_parent,
&mut commands,
&parent_query,
&child_query,
&ancestor_query_2,
true,
);
add_ancestor_markers(
new_parent,
&mut commands,
&parent_query,
&ancestor_query_1,
true,
);
}
}
}
}
}
fn add_ancestor_markers<C: Component>(
entity: Entity,
commands: &mut Commands,
parent_query: &Query<&Parent>,
ancestor_query: &Query<(), With<AncestorMarker<C>>>,
include_self: bool,
) {
if include_self {
commands
.entity(entity)
.insert(AncestorMarker::<C>::default());
}
for parent_entity in parent_query.iter_ancestors(entity) {
if ancestor_query.contains(parent_entity) {
break;
} else {
commands
.entity(parent_entity)
.insert(AncestorMarker::<C>::default());
}
}
}
fn remove_component<T: Bundle>(commands: &mut Commands, entity: Entity) {
if let Some(mut entity_commands) = commands.get_entity(entity) {
entity_commands.remove::<T>();
}
}
#[allow(clippy::type_complexity)]
fn remove_ancestor_markers<C: Component>(
entity: Entity,
commands: &mut Commands,
parent_query: &Query<&Parent>,
child_query: &Query<&Children>,
ancestor_query: &Query<(Entity, Has<C>), Or<(With<AncestorMarker<C>>, With<C>)>>,
include_self: bool,
) {
if include_self {
if let Ok(children) = child_query.get(entity) {
let keep_marker = ancestor_query
.iter_many(children)
.any(|(parent_child, _has_c)| parent_child != entity);
if keep_marker {
return;
} else {
remove_component::<AncestorMarker<C>>(commands, entity);
}
} else {
remove_component::<AncestorMarker<C>>(commands, entity);
}
}
let mut previous_parent = entity;
for parent_entity in parent_query.iter_ancestors(entity) {
if let Ok(children) = child_query.get(parent_entity) {
let keep_marker = ancestor_query
.iter_many(children)
.any(|(child, has_c)| child != previous_parent || (has_c && child != entity));
if keep_marker {
return;
} else {
remove_component::<AncestorMarker<C>>(commands, parent_entity);
}
} else {
remove_component::<AncestorMarker<C>>(commands, entity);
}
previous_parent = parent_entity;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Component)]
struct C;
#[test]
fn add_and_remove_component() {
let mut app = App::new();
app.add_plugins((AncestorMarkerPlugin::<C>::new(PostUpdate), HierarchyPlugin));
let an = app.world_mut().spawn_empty().id();
let bn = app.world_mut().spawn_empty().set_parent(an).id();
let cy = app.world_mut().spawn(C).set_parent(an).id();
let dn = app.world_mut().spawn_empty().set_parent(cy).id();
let en = app.world_mut().spawn_empty().set_parent(cy).id();
let fy = app.world_mut().spawn(C).set_parent(dn).id();
let gy = app.world_mut().spawn(C).set_parent(dn).id();
app.world_mut().run_schedule(PostUpdate);
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(bn).contains::<AncestorMarker<C>>());
assert!(app.world().entity(cy).contains::<AncestorMarker<C>>());
assert!(app.world().entity(dn).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(en).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(fy).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(gy).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(fy);
entity_mut.remove::<C>();
app.world_mut().run_schedule(PostUpdate);
assert!(app.world().entity(dn).contains::<AncestorMarker<C>>());
assert!(app.world().entity(cy).contains::<AncestorMarker<C>>());
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(gy);
entity_mut.remove::<C>();
app.world_mut().run_schedule(PostUpdate);
assert!(!app.world().entity(dn).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(cy).contains::<AncestorMarker<C>>());
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(cy);
entity_mut.remove::<C>();
app.world_mut().run_schedule(PostUpdate);
assert!(!app.world().entity(an).contains::<AncestorMarker<C>>());
}
#[test]
fn remove_children() {
let mut app = App::new();
app.add_plugins((AncestorMarkerPlugin::<C>::new(PostUpdate), HierarchyPlugin));
let an = app.world_mut().spawn_empty().id();
let bn = app.world_mut().spawn_empty().set_parent(an).id();
let cy = app.world_mut().spawn(C).set_parent(an).id();
let dn = app.world_mut().spawn_empty().set_parent(cy).id();
let en = app.world_mut().spawn_empty().set_parent(cy).id();
let fy = app.world_mut().spawn(C).set_parent(dn).id();
let gy = app.world_mut().spawn(C).set_parent(dn).id();
app.world_mut().run_schedule(PostUpdate);
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(bn).contains::<AncestorMarker<C>>());
assert!(app.world().entity(cy).contains::<AncestorMarker<C>>());
assert!(app.world().entity(dn).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(en).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(fy).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(gy).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(dn);
entity_mut.remove_children(&[fy]);
app.world_mut().run_schedule(PostUpdate);
assert!(app.world().entity(dn).contains::<AncestorMarker<C>>());
assert!(app.world().entity(cy).contains::<AncestorMarker<C>>());
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(dn);
entity_mut.remove_children(&[gy]);
app.world_mut().run_schedule(PostUpdate);
assert!(!app.world().entity(dn).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(cy).contains::<AncestorMarker<C>>());
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(an);
entity_mut.remove_children(&[cy]);
app.world_mut().run_schedule(PostUpdate);
assert!(!app.world().entity(an).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(an);
entity_mut.add_child(cy);
app.world_mut().run_schedule(PostUpdate);
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(an);
entity_mut.remove_children(&[cy]);
entity_mut.despawn();
app.world_mut().run_schedule(PostUpdate);
}
#[test]
fn move_children() {
let mut app = App::new();
app.add_plugins((AncestorMarkerPlugin::<C>::new(PostUpdate), HierarchyPlugin));
let an = app.world_mut().spawn_empty().id();
let bn = app.world_mut().spawn_empty().set_parent(an).id();
let cy = app.world_mut().spawn(C).set_parent(an).id();
let dn = app.world_mut().spawn_empty().set_parent(cy).id();
let en = app.world_mut().spawn_empty().set_parent(cy).id();
let fy = app.world_mut().spawn(C).set_parent(dn).id();
let gy = app.world_mut().spawn(C).set_parent(dn).id();
app.world_mut().run_schedule(PostUpdate);
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(bn).contains::<AncestorMarker<C>>());
assert!(app.world().entity(cy).contains::<AncestorMarker<C>>());
assert!(app.world().entity(dn).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(en).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(fy).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(gy).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(bn);
entity_mut.add_child(fy);
app.world_mut().run_schedule(PostUpdate);
assert!(app.world().entity(bn).contains::<AncestorMarker<C>>());
assert!(app.world().entity(dn).contains::<AncestorMarker<C>>());
assert!(app.world().entity(cy).contains::<AncestorMarker<C>>());
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(bn);
entity_mut.add_child(gy);
app.world_mut().run_schedule(PostUpdate);
assert!(!app.world().entity(dn).contains::<AncestorMarker<C>>());
assert!(!app.world().entity(cy).contains::<AncestorMarker<C>>());
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(cy);
entity_mut.push_children(&[fy, gy]);
app.world_mut().run_schedule(PostUpdate);
assert!(!app.world().entity(bn).contains::<AncestorMarker<C>>());
assert!(app.world().entity(cy).contains::<AncestorMarker<C>>());
assert!(app.world().entity(an).contains::<AncestorMarker<C>>());
let mut entity_mut = app.world_mut().entity_mut(bn);
entity_mut.push_children(&[dn, en, fy, gy]);
app.world_mut().run_schedule(PostUpdate);
}
}