use alloc::vec::Vec;
use core::marker::PhantomData;
use crate::{App, Plugin};
#[cfg(feature = "bevy_reflect")]
use bevy_ecs::reflect::ReflectComponent;
use bevy_ecs::{
change_detection::DetectChangesMut,
component::Component,
entity::Entity,
hierarchy::ChildOf,
intern::Interned,
lifecycle::RemovedComponents,
query::{Changed, Or, QueryFilter, With, Without},
relationship::{Relationship, RelationshipTarget},
schedule::{IntoScheduleConfigs, ScheduleLabel, SystemSet},
system::{Commands, Local, Query},
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
pub struct HierarchyPropagatePlugin<
C: Component + Clone + PartialEq,
F: QueryFilter = (),
R: Relationship = ChildOf,
> {
schedule: Interned<dyn ScheduleLabel>,
_marker: PhantomData<fn() -> (C, F, R)>,
}
impl<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>
HierarchyPropagatePlugin<C, F, R>
{
pub fn new(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
_marker: PhantomData,
}
}
}
#[derive(Component, Clone, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Clone, PartialEq)
)]
pub struct Propagate<C: Component + Clone + PartialEq>(pub C);
#[derive(Component)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
pub struct PropagateOver<C>(PhantomData<fn() -> C>);
#[derive(Component)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
pub struct PropagateStop<C>(PhantomData<fn() -> C>);
#[derive(SystemSet, Clone, PartialEq, PartialOrd, Ord)]
pub struct PropagateSet<C: Component + Clone + PartialEq> {
_p: PhantomData<fn() -> C>,
}
#[derive(Component, Clone, PartialEq, Debug)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Clone, PartialEq)
)]
pub struct Inherited<C: Component + Clone + PartialEq>(pub C);
impl<C> Default for PropagateOver<C> {
fn default() -> Self {
Self(Default::default())
}
}
impl<C> Default for PropagateStop<C> {
fn default() -> Self {
Self(Default::default())
}
}
impl<C: Component + Clone + PartialEq> core::fmt::Debug for PropagateSet<C> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PropagateSet")
.field("_p", &self._p)
.finish()
}
}
impl<C: Component + Clone + PartialEq> Eq for PropagateSet<C> {}
impl<C: Component + Clone + PartialEq> core::hash::Hash for PropagateSet<C> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self._p.hash(state);
}
}
impl<C: Component + Clone + PartialEq> Default for PropagateSet<C> {
fn default() -> Self {
Self {
_p: Default::default(),
}
}
}
impl<C: Component + Clone + PartialEq, F: QueryFilter + 'static, R: Relationship> Plugin
for HierarchyPropagatePlugin<C, F, R>
{
fn build(&self, app: &mut App) {
app.add_systems(
self.schedule,
(
update_source::<C, F, R>,
update_reparented::<C, F, R>,
update_removed_limit::<C, F, R>,
propagate_inherited::<C, F, R>,
propagate_output::<C, F>,
)
.chain()
.in_set(PropagateSet::<C>::default()),
);
}
}
pub fn update_source<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
mut commands: Commands,
changed: Query<(Entity, &Propagate<C>), (Or<(Changed<Propagate<C>>, Without<Inherited<C>>)>,)>,
mut removed: RemovedComponents<Propagate<C>>,
relationship: Query<&R>,
relations: Query<&Inherited<C>, Without<PropagateStop<C>>>,
) {
for (entity, source) in &changed {
commands
.entity(entity)
.try_insert(Inherited(source.0.clone()));
}
for removed in removed.read() {
if let Ok(mut commands) = commands.get_entity(removed) {
if let Some(inherited) = relationship
.get(removed)
.ok()
.and_then(|r| relations.get(r.get()).ok())
{
commands.insert(inherited.clone());
} else {
commands.try_remove::<Inherited<C>>();
}
}
}
}
pub fn update_reparented<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
mut commands: Commands,
moved: Query<(Entity, &R, Option<&Inherited<C>>), (Changed<R>, Without<Propagate<C>>, F)>,
relations: Query<&Inherited<C>, Without<PropagateStop<C>>>,
orphaned: Query<Entity, (With<Inherited<C>>, Without<Propagate<C>>, Without<R>, F)>,
) {
for (entity, relation, maybe_inherited) in &moved {
if let Ok(inherited) = relations.get(relation.get()) {
commands.entity(entity).try_insert(inherited.clone());
} else if maybe_inherited.is_some() {
commands.entity(entity).try_remove::<Inherited<C>>();
}
}
for orphan in &orphaned {
commands.entity(orphan).try_remove::<Inherited<C>>();
}
}
pub fn update_removed_limit<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
mut inherited: Query<&mut Inherited<C>>,
mut removed_skip: RemovedComponents<PropagateOver<C>>,
mut removed_stop: RemovedComponents<PropagateStop<C>>,
) {
for entity in removed_skip.read() {
if let Ok(mut inherited) = inherited.get_mut(entity) {
inherited.set_changed();
}
}
for entity in removed_stop.read() {
if let Ok(mut inherited) = inherited.get_mut(entity) {
inherited.set_changed();
}
}
}
pub fn propagate_inherited<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
mut commands: Commands,
changed: Query<
(&Inherited<C>, &R::RelationshipTarget),
(Changed<Inherited<C>>, Without<PropagateStop<C>>, F),
>,
recurse: Query<
(
Option<&R::RelationshipTarget>,
Option<&Inherited<C>>,
Option<&PropagateStop<C>>,
),
(Without<Propagate<C>>, F),
>,
mut removed: RemovedComponents<Inherited<C>>,
mut to_process: Local<Vec<(Entity, Option<Inherited<C>>)>>,
) {
for (inherited, targets) in &changed {
to_process.extend(
targets
.iter()
.map(|target| (target, Some(inherited.clone()))),
);
}
for entity in removed.read() {
if let Ok((Some(targets), _, _)) = recurse.get(entity) {
to_process.extend(targets.iter().map(|target| (target, None)));
}
}
while let Some((entity, maybe_inherited)) = (*to_process).pop() {
let Ok((maybe_targets, maybe_current, maybe_stop)) = recurse.get(entity) else {
continue;
};
if maybe_current == maybe_inherited.as_ref() {
continue;
}
if maybe_stop.is_none()
&& let Some(targets) = maybe_targets
{
to_process.extend(
targets
.iter()
.map(|target| (target, maybe_inherited.clone())),
);
}
if let Some(inherited) = maybe_inherited {
commands.entity(entity).try_insert(inherited);
} else {
commands.entity(entity).try_remove::<Inherited<C>>();
}
}
}
pub fn propagate_output<C: Component + Clone + PartialEq, F: QueryFilter>(
mut commands: Commands,
changed: Query<
(Entity, &Inherited<C>, Option<&C>),
(Changed<Inherited<C>>, Without<PropagateOver<C>>, F),
>,
mut removed: RemovedComponents<Inherited<C>>,
skip: Query<(), With<PropagateOver<C>>>,
) {
for (entity, inherited, maybe_current) in &changed {
if maybe_current.is_some_and(|c| &inherited.0 == c) {
continue;
}
commands.entity(entity).try_insert(inherited.0.clone());
}
for removed in removed.read() {
if skip.get(removed).is_err() {
commands.entity(removed).try_remove::<C>();
}
}
}
#[cfg(test)]
mod tests {
use bevy_ecs::schedule::Schedule;
use crate::{App, Update};
use super::*;
#[derive(Component, Clone, PartialEq, Debug)]
struct TestValue(u32);
#[test]
fn test_simple_propagate() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let intermediate = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(intermediate))
.id();
app.update();
assert_eq!(
query.get_many(app.world(), [propagator, intermediate, propagatee]),
Ok([&TestValue(1), &TestValue(1), &TestValue(1)])
);
}
#[test]
fn test_remove_propagate() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
app.update();
assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
app.world_mut()
.commands()
.entity(propagator)
.remove::<Propagate<TestValue>>();
app.update();
assert!(query.get(app.world(), propagator).is_err());
assert!(query.get(app.world(), propagatee).is_err());
}
#[test]
fn test_remove_orphan() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
app.update();
assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
app.world_mut()
.commands()
.entity(propagatee)
.remove::<ChildOf>();
app.update();
assert!(query.get(app.world(), propagatee).is_err());
}
#[test]
fn test_reparented() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator_one = app.world_mut().spawn(Propagate(TestValue(1))).id();
let other_parent = app.world_mut().spawn_empty().id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator_one))
.id();
app.update();
assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
app.world_mut()
.commands()
.entity(propagatee)
.insert(ChildOf(other_parent));
app.update();
assert!(query.get(app.world(), propagatee).is_err());
}
#[test]
fn test_reparented_with_prior() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator_a = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagator_b = app.world_mut().spawn(Propagate(TestValue(2))).id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator_a))
.id();
app.update();
assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
app.world_mut()
.commands()
.entity(propagatee)
.insert(ChildOf(propagator_b));
app.update();
assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(2)));
}
#[test]
fn test_propagate_over() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagate_over = app
.world_mut()
.spawn(TestValue(2))
.insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
.id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagate_over))
.id();
app.update();
assert_eq!(query.get(app.world(), propagate_over), Ok(&TestValue(2)));
assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
}
#[test]
fn test_remove_propagate_over() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagate_over = app
.world_mut()
.spawn(TestValue(2))
.insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
.id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagate_over))
.id();
app.update();
assert_eq!(
app.world_mut()
.query::<&Inherited<TestValue>>()
.get(app.world(), propagate_over),
Ok(&Inherited(TestValue(1)))
);
assert_eq!(
app.world_mut()
.query::<&Inherited<TestValue>>()
.get(app.world(), propagatee),
Ok(&Inherited(TestValue(1)))
);
assert_eq!(
query.get_many(app.world(), [propagate_over, propagatee]),
Ok([&TestValue(2), &TestValue(1)])
);
app.world_mut()
.commands()
.entity(propagate_over)
.remove::<PropagateOver<TestValue>>();
app.update();
assert_eq!(
query.get_many(app.world(), [propagate_over, propagatee]),
Ok([&TestValue(1), &TestValue(1)])
);
}
#[test]
fn test_propagate_over_parent_removed() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagate_over = app
.world_mut()
.spawn(TestValue(2))
.insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
.id();
app.update();
assert_eq!(
query.get_many(app.world(), [propagator, propagate_over]),
Ok([&TestValue(1), &TestValue(2)])
);
app.world_mut()
.commands()
.entity(propagator)
.remove::<Propagate<TestValue>>();
app.update();
assert!(query.get(app.world(), propagator).is_err(),);
assert_eq!(query.get(app.world(), propagate_over), Ok(&TestValue(2)));
}
#[test]
fn test_orphaned_propagate_over() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagate_over = app
.world_mut()
.spawn(TestValue(2))
.insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
.id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagate_over))
.id();
app.update();
assert_eq!(
query.get_many(app.world(), [propagate_over, propagatee]),
Ok([&TestValue(2), &TestValue(1)])
);
app.world_mut()
.commands()
.entity(propagate_over)
.remove::<ChildOf>();
app.update();
assert_eq!(query.get(app.world(), propagate_over), Ok(&TestValue(2)));
assert!(query.get(app.world(), propagatee).is_err());
}
#[test]
fn test_propagate_stop() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagate_stop = app
.world_mut()
.spawn(PropagateStop::<TestValue>::default())
.insert(ChildOf(propagator))
.id();
let no_propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagate_stop))
.id();
app.update();
assert_eq!(query.get(app.world(), propagate_stop), Ok(&TestValue(1)));
assert!(query.get(app.world(), no_propagatee).is_err());
}
#[test]
fn test_remove_propagate_stop() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagate_stop = app
.world_mut()
.spawn(PropagateStop::<TestValue>::default())
.insert(ChildOf(propagator))
.id();
let no_propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagate_stop))
.id();
app.update();
assert_eq!(query.get(app.world(), propagate_stop), Ok(&TestValue(1)));
assert!(query.get(app.world(), no_propagatee).is_err());
app.world_mut()
.commands()
.entity(propagate_stop)
.remove::<PropagateStop<TestValue>>();
app.update();
assert_eq!(
query.get_many(app.world(), [propagate_stop, no_propagatee]),
Ok([&TestValue(1), &TestValue(1)])
);
}
#[test]
fn test_intermediate_override() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let intermediate = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(intermediate))
.id();
app.update();
assert_eq!(
query.get_many(app.world(), [propagator, intermediate, propagatee]),
Ok([&TestValue(1), &TestValue(1), &TestValue(1)])
);
app.world_mut()
.entity_mut(intermediate)
.insert(Propagate(TestValue(2)));
app.update();
assert_eq!(
app.world_mut()
.query::<&TestValue>()
.get_many(app.world(), [propagator, intermediate, propagatee]),
Ok([&TestValue(1), &TestValue(2), &TestValue(2)])
);
}
#[test]
fn test_filter() {
#[derive(Component)]
struct Marker;
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue, With<Marker>>::new(
Update,
));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
app.update();
assert!(query.get(app.world(), propagator).is_err());
assert!(query.get(app.world(), propagatee).is_err());
app.world_mut().entity_mut(propagator).insert(Marker);
app.update();
assert!(query.get(app.world(), propagator).is_err());
assert!(query.get(app.world(), propagatee).is_err());
app.world_mut()
.entity_mut(propagator)
.insert(Propagate(TestValue(1)));
app.update();
assert_eq!(query.get(app.world(), propagator), Ok(&TestValue(1)));
assert!(query.get(app.world(), propagatee).is_err());
app.world_mut().entity_mut(propagatee).insert(Marker);
app.update();
assert_eq!(query.get(app.world(), propagator), Ok(&TestValue(1)));
assert!(query.get(app.world(), propagatee).is_err());
app.world_mut()
.entity_mut(propagator)
.insert(Propagate(TestValue(1)));
app.update();
assert_eq!(
app.world_mut()
.query::<&TestValue>()
.get_many(app.world(), [propagator, propagatee]),
Ok([&TestValue(1), &TestValue(1)])
);
}
#[test]
fn test_removed_propagate_still_inherits() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagatee = app
.world_mut()
.spawn(Propagate(TestValue(2)))
.insert(ChildOf(propagator))
.id();
app.update();
assert_eq!(
query.get_many(app.world(), [propagator, propagatee]),
Ok([&TestValue(1), &TestValue(2)])
);
app.world_mut()
.commands()
.entity(propagatee)
.remove::<Propagate<TestValue>>();
app.update();
assert_eq!(
query.get_many(app.world(), [propagator, propagatee]),
Ok([&TestValue(1), &TestValue(1)])
);
}
#[test]
fn test_reparent_respects_stop() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
let mut query = app.world_mut().query::<&TestValue>();
let propagator = app
.world_mut()
.spawn((
Propagate(TestValue(1)),
PropagateStop::<TestValue>::default(),
))
.id();
let propagatee = app.world_mut().spawn(TestValue(2)).id();
app.update();
assert_eq!(
query.get_many(app.world(), [propagator, propagatee]),
Ok([&TestValue(1), &TestValue(2)])
);
app.world_mut()
.commands()
.entity(propagatee)
.insert(ChildOf(propagator));
app.update();
assert_eq!(
query.get_many(app.world(), [propagator, propagatee]),
Ok([&TestValue(1), &TestValue(2)])
);
}
}