Skip to main content

bevy_app/
propagate.rs

1use alloc::vec::Vec;
2use core::marker::PhantomData;
3
4use crate::{App, Plugin};
5#[cfg(feature = "bevy_reflect")]
6use bevy_ecs::reflect::ReflectComponent;
7use bevy_ecs::{
8    change_detection::DetectChangesMut,
9    component::Component,
10    entity::Entity,
11    hierarchy::ChildOf,
12    intern::Interned,
13    lifecycle::{Insert, Remove, RemovedComponents},
14    observer::On,
15    query::{Changed, Has, Or, QueryFilter, With, Without},
16    relationship::{Relationship, RelationshipTarget},
17    schedule::{IntoScheduleConfigs, ScheduleLabel, SystemSet},
18    system::{Commands, Local, Query},
19    template::{FromTemplate, Template},
20};
21#[cfg(feature = "bevy_reflect")]
22use bevy_reflect::Reflect;
23
24/// Plugin to automatically propagate a component value to all direct and transient relationship
25/// targets (e.g. [`bevy_ecs::hierarchy::Children`]) of entities with a [`Propagate`] component.
26///
27/// The plugin Will maintain the target component over hierarchy changes, adding or removing
28/// `C` when a relationship `R` (e.g. [`ChildOf`]) is added to or removed from a
29/// relationship tree with a [`Propagate<C>`] source, or if the [`Propagate<C>`] component
30/// is added, changed or removed.
31///
32/// Optionally you can include a query filter `F` to restrict the entities that are updated.
33/// Note that the filter is not rechecked dynamically: changes to the filter state will not be
34/// picked up until the  [`Propagate`] component is touched, or the hierarchy is changed.
35/// All members of the tree between source and target must match the filter for propagation
36/// to reach a given target.
37/// Individual entities can be skipped or terminate the propagation with the [`PropagateOver`]
38/// and [`PropagateStop`] components.
39///
40/// The schedule can be configured via [`HierarchyPropagatePlugin::new`].
41/// You should be sure to schedule your logic relative to this set: making changes
42/// that modify component values before this logic, and reading the propagated
43/// values after it.
44pub struct HierarchyPropagatePlugin<
45    C: Component + Clone + PartialEq,
46    F: QueryFilter = (),
47    R: Relationship = ChildOf,
48> {
49    schedule: Interned<dyn ScheduleLabel>,
50    _marker: PhantomData<fn() -> (C, F, R)>,
51}
52
53impl<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>
54    HierarchyPropagatePlugin<C, F, R>
55{
56    /// Construct the plugin. The propagation systems will be placed in the specified schedule.
57    pub fn new(schedule: impl ScheduleLabel) -> Self {
58        Self {
59            schedule: schedule.intern(),
60            _marker: PhantomData,
61        }
62    }
63}
64
65/// Causes the inner component to be added to this entity and all direct and transient relationship
66/// targets. A target with a [`Propagate<C>`] component of its own will override propagation from
67/// that point in the tree.
68///
69/// The propagation is managed by the [`HierarchyPropagatePlugin`]. See its documentation for details
70/// on how propagation is configured and behaves.
71#[derive(Component, Clone, PartialEq)]
72#[cfg_attr(
73    feature = "bevy_reflect",
74    derive(Reflect),
75    reflect(Component, Clone, PartialEq)
76)]
77pub struct Propagate<C: Component + Clone + PartialEq>(pub C);
78
79/// The [`Template`] for [`Propagate`].
80pub struct PropagateTemplate<T>(pub T);
81
82impl<T: Default> Default for PropagateTemplate<T> {
83    fn default() -> Self {
84        Self(Default::default())
85    }
86}
87
88impl<C: FromTemplate + Component + Clone + PartialEq> FromTemplate for Propagate<C> {
89    type Template = PropagateTemplate<C::Template>;
90}
91
92impl<C: Template<Output: Component + Clone + PartialEq>> Template for PropagateTemplate<C> {
93    type Output = Propagate<C::Output>;
94
95    fn build_template(
96        &self,
97        context: &mut bevy_ecs::template::TemplateContext,
98    ) -> bevy_ecs::error::Result<Self::Output> {
99        Ok(Propagate(self.0.build_template(context)?))
100    }
101
102    fn clone_template(&self) -> Self {
103        PropagateTemplate(self.0.clone_template())
104    }
105}
106
107/// Stops the output component being added to this entity.
108/// Relationship targets will still inherit the component from this entity or its parents.
109///
110/// This is used to control propagation configured via the [`HierarchyPropagatePlugin`]. See its
111/// documentation for details on how propagation is configured and behaves.
112#[derive(Component, Clone)]
113#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
114pub struct PropagateOver<C>(PhantomData<fn() -> C>);
115
116/// Stops the propagation at this entity. Children will not inherit the component.
117///
118/// This is used to control propagation configured via the [`HierarchyPropagatePlugin`]. See its
119/// documentation for details on how propagation is configured and behaves.
120#[derive(Component, Clone)]
121#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
122pub struct PropagateStop<C>(PhantomData<fn() -> C>);
123
124/// The set in which propagation systems are added. You can schedule your logic relative to this set.
125///
126/// This is used with propagation configured via the [`HierarchyPropagatePlugin`]. See its
127/// documentation for details on how propagation is configured and behaves.
128#[derive(SystemSet, Clone, PartialEq, PartialOrd, Ord)]
129pub struct PropagateSet<C: Component + Clone + PartialEq> {
130    _p: PhantomData<fn() -> C>,
131}
132
133/// Internal struct for managing propagation
134#[derive(Component, Clone, PartialEq, Debug)]
135#[cfg_attr(
136    feature = "bevy_reflect",
137    derive(Reflect),
138    reflect(Component, Clone, PartialEq)
139)]
140pub struct Inherited<C: Component + Clone + PartialEq>(pub C);
141
142impl<C> Default for PropagateOver<C> {
143    fn default() -> Self {
144        Self(Default::default())
145    }
146}
147
148impl<C> Default for PropagateStop<C> {
149    fn default() -> Self {
150        Self(Default::default())
151    }
152}
153
154impl<C: Component + Clone + PartialEq> core::fmt::Debug for PropagateSet<C> {
155    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
156        f.debug_struct("PropagateSet")
157            .field("_p", &self._p)
158            .finish()
159    }
160}
161
162impl<C: Component + Clone + PartialEq> Eq for PropagateSet<C> {}
163
164impl<C: Component + Clone + PartialEq> core::hash::Hash for PropagateSet<C> {
165    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
166        self._p.hash(state);
167    }
168}
169
170impl<C: Component + Clone + PartialEq> Default for PropagateSet<C> {
171    fn default() -> Self {
172        Self {
173            _p: Default::default(),
174        }
175    }
176}
177
178impl<C: Component + Clone + PartialEq, F: QueryFilter + 'static, R: Relationship> Plugin
179    for HierarchyPropagatePlugin<C, F, R>
180{
181    fn build(&self, app: &mut App) {
182        app.add_systems(
183            self.schedule,
184            (
185                update_source::<C, F, R>,
186                update_removed_limit::<C, F, R>,
187                propagate_inherited::<C, F, R>,
188                propagate_output::<C, F>,
189            )
190                .chain()
191                .in_set(PropagateSet::<C>::default()),
192        );
193        app.add_observer(on_r_inserted::<C, F, R>);
194        app.add_observer(on_r_removed::<C, F, R>);
195    }
196}
197
198/// add/remove `Inherited::<C>` for entities with a direct `Propagate::<C>`
199pub fn update_source<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
200    mut commands: Commands,
201    changed: Query<(Entity, &Propagate<C>), (Or<(Changed<Propagate<C>>, Without<Inherited<C>>)>,)>,
202    mut removed: RemovedComponents<Propagate<C>>,
203    relationship: Query<&R>,
204    relations: Query<&Inherited<C>, Without<PropagateStop<C>>>,
205    sources: Query<(), With<Propagate<C>>>,
206) {
207    for (entity, source) in &changed {
208        commands
209            .entity(entity)
210            .try_insert(Inherited(source.0.clone()));
211    }
212
213    // set `Inherited::<C>` based on ancestry when `Propagate::<C>` is removed
214    for removed in removed.read() {
215        if !sources.contains(removed)
216            && let Ok(mut commands) = commands.get_entity(removed)
217        {
218            if let Some(inherited) = relationship
219                .get(removed)
220                .ok()
221                .and_then(|r| relations.get(r.get()).ok())
222            {
223                commands.insert(inherited.clone());
224            } else {
225                commands.try_remove::<Inherited<C>>();
226            }
227        }
228    }
229}
230
231/// Add/remove [`Inherited::<C>`] when an entity gains or changes its `R` relationship
232pub fn on_r_inserted<
233    C: Component + Clone + PartialEq,
234    F: QueryFilter + 'static,
235    R: Relationship,
236>(
237    event: On<Insert<R>>,
238    mut commands: Commands,
239    query: Query<(&R, Has<Inherited<C>>), (Without<Propagate<C>>, F)>,
240    relations: Query<&Inherited<C>, Without<PropagateStop<C>>>,
241) {
242    let Ok((relation, has_inherited)) = query.get(event.entity) else {
243        return;
244    };
245    if let Ok(inherited) = relations.get(relation.get()) {
246        commands.entity(event.entity).try_insert(inherited.clone());
247    } else if has_inherited {
248        commands.entity(event.entity).try_remove::<Inherited<C>>();
249    }
250}
251
252/// Remove [`Inherited::<C>`] when an entity loses its `R` relationship
253pub fn on_r_removed<C: Component + Clone + PartialEq, F: QueryFilter + 'static, R: Relationship>(
254    event: On<Remove<R>>,
255    mut commands: Commands,
256    query: Query<(), (With<Inherited<C>>, Without<Propagate<C>>, F)>,
257) {
258    if query.contains(event.entity) {
259        commands.entity(event.entity).try_remove::<Inherited<C>>();
260    }
261}
262
263/// When `PropagateOver` or `PropagateStop` is removed, update the `Inherited::<C>` to trigger propagation
264pub fn update_removed_limit<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
265    mut inherited: Query<&mut Inherited<C>>,
266    mut removed_skip: RemovedComponents<PropagateOver<C>>,
267    mut removed_stop: RemovedComponents<PropagateStop<C>>,
268) {
269    for entity in removed_skip.read() {
270        if let Ok(mut inherited) = inherited.get_mut(entity) {
271            inherited.set_changed();
272        }
273    }
274    for entity in removed_stop.read() {
275        if let Ok(mut inherited) = inherited.get_mut(entity) {
276            inherited.set_changed();
277        }
278    }
279}
280
281/// add/remove `Inherited::<C>` for targets of entities with modified `Inherited::<C>`
282pub fn propagate_inherited<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
283    mut commands: Commands,
284    changed: Query<
285        (&Inherited<C>, &R::RelationshipTarget),
286        (Changed<Inherited<C>>, Without<PropagateStop<C>>, F),
287    >,
288    recurse: Query<
289        (
290            Option<&R::RelationshipTarget>,
291            Option<&Inherited<C>>,
292            Option<&PropagateStop<C>>,
293        ),
294        (Without<Propagate<C>>, F),
295    >,
296    mut removed: RemovedComponents<Inherited<C>>,
297    mut to_process: Local<Vec<(Entity, Option<Inherited<C>>)>>,
298) {
299    // gather changed
300    for (inherited, targets) in &changed {
301        to_process.extend(
302            targets
303                .iter()
304                .map(|target| (target, Some(inherited.clone()))),
305        );
306    }
307
308    // and removed
309    for entity in removed.read() {
310        if let Ok((Some(targets), None, _)) = recurse.get(entity) {
311            to_process.extend(targets.iter().map(|target| (target, None)));
312        }
313    }
314
315    // propagate
316    while let Some((entity, maybe_inherited)) = (*to_process).pop() {
317        let Ok((maybe_targets, maybe_current, maybe_stop)) = recurse.get(entity) else {
318            continue;
319        };
320
321        if maybe_current == maybe_inherited.as_ref() {
322            continue;
323        }
324
325        // update children if required
326        if maybe_stop.is_none()
327            && let Some(targets) = maybe_targets
328        {
329            to_process.extend(
330                targets
331                    .iter()
332                    .map(|target| (target, maybe_inherited.clone())),
333            );
334        }
335
336        // update this node's `Inherited<C>`
337        if let Some(inherited) = maybe_inherited {
338            commands.entity(entity).try_insert(inherited);
339        } else {
340            commands.entity(entity).try_remove::<Inherited<C>>();
341        }
342    }
343}
344
345/// add/remove `C` on entities with `Inherited::<C>`
346pub fn propagate_output<C: Component + Clone + PartialEq, F: QueryFilter>(
347    mut commands: Commands,
348    changed: Query<
349        (Entity, &Inherited<C>, Option<&C>),
350        (Changed<Inherited<C>>, Without<PropagateOver<C>>, F),
351    >,
352    mut inherited_removed: RemovedComponents<Inherited<C>>,
353    without_propagation_components: Query<(), (Without<PropagateOver<C>>, Without<Inherited<C>>)>,
354) {
355    for (entity, inherited, maybe_current) in &changed {
356        if maybe_current.is_some_and(|c| &inherited.0 == c) {
357            continue;
358        }
359
360        commands.entity(entity).try_insert(inherited.0.clone());
361    }
362
363    for inherited_removed in inherited_removed.read() {
364        // Skip removal if propagation components were re-added this update
365        if without_propagation_components.contains(inherited_removed) {
366            commands.entity(inherited_removed).try_remove::<C>();
367        }
368    }
369}
370
371#[cfg(test)]
372mod tests {
373    use bevy_ecs::schedule::Schedule;
374
375    use crate::{App, Update};
376
377    use super::*;
378
379    #[derive(Component, Clone, PartialEq, Debug)]
380    struct TestValue(u32);
381
382    #[test]
383    fn test_simple_propagate() {
384        let mut app = App::new();
385        app.add_schedule(Schedule::new(Update));
386        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
387
388        let mut query = app.world_mut().query::<&TestValue>();
389
390        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
391        let intermediate = app
392            .world_mut()
393            .spawn_empty()
394            .insert(ChildOf(propagator))
395            .id();
396        let propagatee = app
397            .world_mut()
398            .spawn_empty()
399            .insert(ChildOf(intermediate))
400            .id();
401
402        app.update();
403
404        assert_eq!(
405            query.get_many(app.world(), [propagator, intermediate, propagatee]),
406            Ok([&TestValue(1), &TestValue(1), &TestValue(1)])
407        );
408    }
409
410    #[test]
411    fn test_remove_propagate() {
412        let mut app = App::new();
413        app.add_schedule(Schedule::new(Update));
414        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
415
416        let mut query = app.world_mut().query::<&TestValue>();
417
418        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
419        let propagatee = app
420            .world_mut()
421            .spawn_empty()
422            .insert(ChildOf(propagator))
423            .id();
424
425        app.update();
426
427        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
428
429        app.world_mut()
430            .commands()
431            .entity(propagator)
432            .remove::<Propagate<TestValue>>();
433        app.update();
434
435        assert!(query.get(app.world(), propagator).is_err());
436        assert!(query.get(app.world(), propagatee).is_err());
437    }
438
439    #[test]
440    fn test_remove_and_reinsert_propagate() {
441        let mut app = App::new();
442        app.add_schedule(Schedule::new(Update));
443        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
444
445        let parent = app.world_mut().spawn(Propagate(TestValue(1))).id();
446        let child = app.world_mut().spawn_empty().insert(ChildOf(parent)).id();
447
448        app.update();
449
450        app.world_mut()
451            .entity_mut(parent)
452            .remove::<Propagate<TestValue>>()
453            .insert(Propagate(TestValue(2)));
454
455        app.update();
456
457        assert_eq!(
458            app.world_mut()
459                .query::<&TestValue>()
460                .get_many(app.world(), [parent, child]),
461            Ok([&TestValue(2), &TestValue(2)])
462        );
463    }
464
465    #[test]
466    fn test_remove_orphan() {
467        let mut app = App::new();
468        app.add_schedule(Schedule::new(Update));
469        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
470
471        let mut query = app.world_mut().query::<&TestValue>();
472
473        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
474        let propagatee = app
475            .world_mut()
476            .spawn_empty()
477            .insert(ChildOf(propagator))
478            .id();
479
480        app.update();
481
482        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
483
484        app.world_mut()
485            .commands()
486            .entity(propagatee)
487            .remove::<ChildOf>();
488        app.update();
489
490        assert!(query.get(app.world(), propagatee).is_err());
491    }
492
493    #[test]
494    fn test_reparented() {
495        let mut app = App::new();
496        app.add_schedule(Schedule::new(Update));
497        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
498
499        let mut query = app.world_mut().query::<&TestValue>();
500
501        let propagator_one = app.world_mut().spawn(Propagate(TestValue(1))).id();
502        let other_parent = app.world_mut().spawn_empty().id();
503        let propagatee = app
504            .world_mut()
505            .spawn_empty()
506            .insert(ChildOf(propagator_one))
507            .id();
508
509        app.update();
510
511        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
512
513        app.world_mut()
514            .commands()
515            .entity(propagatee)
516            .insert(ChildOf(other_parent));
517
518        app.update();
519
520        assert!(query.get(app.world(), propagatee).is_err());
521    }
522
523    #[test]
524    fn test_reparented_with_prior() {
525        let mut app = App::new();
526        app.add_schedule(Schedule::new(Update));
527        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
528
529        let mut query = app.world_mut().query::<&TestValue>();
530
531        let propagator_a = app.world_mut().spawn(Propagate(TestValue(1))).id();
532        let propagator_b = app.world_mut().spawn(Propagate(TestValue(2))).id();
533        let propagatee = app
534            .world_mut()
535            .spawn_empty()
536            .insert(ChildOf(propagator_a))
537            .id();
538
539        app.update();
540
541        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
542
543        app.world_mut()
544            .commands()
545            .entity(propagatee)
546            .insert(ChildOf(propagator_b));
547        app.update();
548
549        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(2)));
550    }
551
552    #[test]
553    fn test_detach_and_reattach_propagates_to_descendants() {
554        let mut app = App::new();
555        app.add_schedule(Schedule::new(Update));
556        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
557
558        let mut query = app.world_mut().query::<&TestValue>();
559
560        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
561        let intermediate = app
562            .world_mut()
563            .spawn_empty()
564            .insert(ChildOf(propagator))
565            .id();
566        let propagatee = app
567            .world_mut()
568            .spawn_empty()
569            .insert(ChildOf(intermediate))
570            .id();
571
572        app.update();
573
574        assert_eq!(
575            query.get_many(app.world(), [intermediate, propagatee]),
576            Ok([&TestValue(1), &TestValue(1)])
577        );
578
579        app.world_mut()
580            .entity_mut(intermediate)
581            .remove::<ChildOf>()
582            .insert(ChildOf(propagator));
583        app.update();
584
585        assert_eq!(
586            query.get_many(app.world(), [intermediate, propagatee]),
587            Ok([&TestValue(1), &TestValue(1)])
588        );
589    }
590
591    #[test]
592    fn test_propagate_over() {
593        let mut app = App::new();
594        app.add_schedule(Schedule::new(Update));
595        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
596
597        let mut query = app.world_mut().query::<&TestValue>();
598
599        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
600        let propagate_over = app
601            .world_mut()
602            .spawn(TestValue(2))
603            .insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
604            .id();
605        let propagatee = app
606            .world_mut()
607            .spawn_empty()
608            .insert(ChildOf(propagate_over))
609            .id();
610
611        app.update();
612
613        assert_eq!(query.get(app.world(), propagate_over), Ok(&TestValue(2)));
614        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
615    }
616
617    #[test]
618    fn test_remove_propagate_over() {
619        let mut app = App::new();
620        app.add_schedule(Schedule::new(Update));
621        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
622
623        let mut query = app.world_mut().query::<&TestValue>();
624
625        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
626        let propagate_over = app
627            .world_mut()
628            .spawn(TestValue(2))
629            .insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
630            .id();
631        let propagatee = app
632            .world_mut()
633            .spawn_empty()
634            .insert(ChildOf(propagate_over))
635            .id();
636
637        app.update();
638        assert_eq!(
639            app.world_mut()
640                .query::<&Inherited<TestValue>>()
641                .get(app.world(), propagate_over),
642            Ok(&Inherited(TestValue(1)))
643        );
644        assert_eq!(
645            app.world_mut()
646                .query::<&Inherited<TestValue>>()
647                .get(app.world(), propagatee),
648            Ok(&Inherited(TestValue(1)))
649        );
650        assert_eq!(
651            query.get_many(app.world(), [propagate_over, propagatee]),
652            Ok([&TestValue(2), &TestValue(1)])
653        );
654
655        app.world_mut()
656            .commands()
657            .entity(propagate_over)
658            .remove::<PropagateOver<TestValue>>();
659        app.update();
660
661        assert_eq!(
662            query.get_many(app.world(), [propagate_over, propagatee]),
663            Ok([&TestValue(1), &TestValue(1)])
664        );
665    }
666
667    #[test]
668    fn test_propagate_over_parent_removed() {
669        let mut app = App::new();
670        app.add_schedule(Schedule::new(Update));
671        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
672
673        let mut query = app.world_mut().query::<&TestValue>();
674
675        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
676        let propagate_over = app
677            .world_mut()
678            .spawn(TestValue(2))
679            .insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
680            .id();
681
682        app.update();
683
684        assert_eq!(
685            query.get_many(app.world(), [propagator, propagate_over]),
686            Ok([&TestValue(1), &TestValue(2)])
687        );
688
689        app.world_mut()
690            .commands()
691            .entity(propagator)
692            .remove::<Propagate<TestValue>>();
693        app.update();
694
695        assert!(query.get(app.world(), propagator).is_err(),);
696        assert_eq!(query.get(app.world(), propagate_over), Ok(&TestValue(2)));
697    }
698
699    #[test]
700    fn test_orphaned_propagate_over() {
701        let mut app = App::new();
702        app.add_schedule(Schedule::new(Update));
703        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
704
705        let mut query = app.world_mut().query::<&TestValue>();
706
707        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
708        let propagate_over = app
709            .world_mut()
710            .spawn(TestValue(2))
711            .insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
712            .id();
713        let propagatee = app
714            .world_mut()
715            .spawn_empty()
716            .insert(ChildOf(propagate_over))
717            .id();
718
719        app.update();
720
721        assert_eq!(
722            query.get_many(app.world(), [propagate_over, propagatee]),
723            Ok([&TestValue(2), &TestValue(1)])
724        );
725
726        app.world_mut()
727            .commands()
728            .entity(propagate_over)
729            .remove::<ChildOf>();
730        app.update();
731
732        assert_eq!(query.get(app.world(), propagate_over), Ok(&TestValue(2)));
733        assert!(query.get(app.world(), propagatee).is_err());
734    }
735
736    #[test]
737    fn test_propagate_stop() {
738        let mut app = App::new();
739        app.add_schedule(Schedule::new(Update));
740        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
741
742        let mut query = app.world_mut().query::<&TestValue>();
743
744        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
745        let propagate_stop = app
746            .world_mut()
747            .spawn(PropagateStop::<TestValue>::default())
748            .insert(ChildOf(propagator))
749            .id();
750        let no_propagatee = app
751            .world_mut()
752            .spawn_empty()
753            .insert(ChildOf(propagate_stop))
754            .id();
755
756        app.update();
757
758        assert_eq!(query.get(app.world(), propagate_stop), Ok(&TestValue(1)));
759        assert!(query.get(app.world(), no_propagatee).is_err());
760    }
761
762    #[test]
763    fn test_remove_propagate_stop() {
764        let mut app = App::new();
765        app.add_schedule(Schedule::new(Update));
766        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
767
768        let mut query = app.world_mut().query::<&TestValue>();
769
770        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
771        let propagate_stop = app
772            .world_mut()
773            .spawn(PropagateStop::<TestValue>::default())
774            .insert(ChildOf(propagator))
775            .id();
776        let no_propagatee = app
777            .world_mut()
778            .spawn_empty()
779            .insert(ChildOf(propagate_stop))
780            .id();
781
782        app.update();
783
784        assert_eq!(query.get(app.world(), propagate_stop), Ok(&TestValue(1)));
785        assert!(query.get(app.world(), no_propagatee).is_err());
786
787        app.world_mut()
788            .commands()
789            .entity(propagate_stop)
790            .remove::<PropagateStop<TestValue>>();
791        app.update();
792
793        assert_eq!(
794            query.get_many(app.world(), [propagate_stop, no_propagatee]),
795            Ok([&TestValue(1), &TestValue(1)])
796        );
797    }
798
799    #[test]
800    fn test_intermediate_override() {
801        let mut app = App::new();
802        app.add_schedule(Schedule::new(Update));
803        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
804
805        let mut query = app.world_mut().query::<&TestValue>();
806
807        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
808        let intermediate = app
809            .world_mut()
810            .spawn_empty()
811            .insert(ChildOf(propagator))
812            .id();
813        let propagatee = app
814            .world_mut()
815            .spawn_empty()
816            .insert(ChildOf(intermediate))
817            .id();
818
819        app.update();
820
821        assert_eq!(
822            query.get_many(app.world(), [propagator, intermediate, propagatee]),
823            Ok([&TestValue(1), &TestValue(1), &TestValue(1)])
824        );
825
826        app.world_mut()
827            .entity_mut(intermediate)
828            .insert(Propagate(TestValue(2)));
829        app.update();
830
831        assert_eq!(
832            app.world_mut()
833                .query::<&TestValue>()
834                .get_many(app.world(), [propagator, intermediate, propagatee]),
835            Ok([&TestValue(1), &TestValue(2), &TestValue(2)])
836        );
837    }
838
839    #[test]
840    fn test_filter() {
841        #[derive(Component)]
842        struct Marker;
843
844        let mut app = App::new();
845        app.add_schedule(Schedule::new(Update));
846        app.add_plugins(HierarchyPropagatePlugin::<TestValue, With<Marker>>::new(
847            Update,
848        ));
849
850        let mut query = app.world_mut().query::<&TestValue>();
851
852        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
853        let propagatee = app
854            .world_mut()
855            .spawn_empty()
856            .insert(ChildOf(propagator))
857            .id();
858
859        app.update();
860
861        assert!(query.get(app.world(), propagator).is_err());
862        assert!(query.get(app.world(), propagatee).is_err());
863
864        // NOTE: changes to the filter condition are not rechecked
865        app.world_mut().entity_mut(propagator).insert(Marker);
866        app.update();
867
868        assert!(query.get(app.world(), propagator).is_err());
869        assert!(query.get(app.world(), propagatee).is_err());
870
871        app.world_mut()
872            .entity_mut(propagator)
873            .insert(Propagate(TestValue(1)));
874        app.update();
875
876        assert_eq!(query.get(app.world(), propagator), Ok(&TestValue(1)));
877        assert!(query.get(app.world(), propagatee).is_err());
878
879        app.world_mut().entity_mut(propagatee).insert(Marker);
880        app.update();
881
882        assert_eq!(query.get(app.world(), propagator), Ok(&TestValue(1)));
883        assert!(query.get(app.world(), propagatee).is_err());
884
885        app.world_mut()
886            .entity_mut(propagator)
887            .insert(Propagate(TestValue(1)));
888        app.update();
889
890        assert_eq!(
891            app.world_mut()
892                .query::<&TestValue>()
893                .get_many(app.world(), [propagator, propagatee]),
894            Ok([&TestValue(1), &TestValue(1)])
895        );
896    }
897
898    #[test]
899    fn test_removed_propagate_still_inherits() {
900        let mut app = App::new();
901        app.add_schedule(Schedule::new(Update));
902        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
903
904        let mut query = app.world_mut().query::<&TestValue>();
905
906        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
907        let propagatee = app
908            .world_mut()
909            .spawn(Propagate(TestValue(2)))
910            .insert(ChildOf(propagator))
911            .id();
912
913        app.update();
914
915        assert_eq!(
916            query.get_many(app.world(), [propagator, propagatee]),
917            Ok([&TestValue(1), &TestValue(2)])
918        );
919
920        app.world_mut()
921            .commands()
922            .entity(propagatee)
923            .remove::<Propagate<TestValue>>();
924        app.update();
925
926        assert_eq!(
927            query.get_many(app.world(), [propagator, propagatee]),
928            Ok([&TestValue(1), &TestValue(1)])
929        );
930    }
931
932    #[test]
933    fn test_reparent_respects_stop() {
934        let mut app = App::new();
935        app.add_schedule(Schedule::new(Update));
936        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
937
938        let mut query = app.world_mut().query::<&TestValue>();
939
940        let propagator = app
941            .world_mut()
942            .spawn((
943                Propagate(TestValue(1)),
944                PropagateStop::<TestValue>::default(),
945            ))
946            .id();
947        let propagatee = app.world_mut().spawn(TestValue(2)).id();
948
949        app.update();
950
951        assert_eq!(
952            query.get_many(app.world(), [propagator, propagatee]),
953            Ok([&TestValue(1), &TestValue(2)])
954        );
955
956        app.world_mut()
957            .commands()
958            .entity(propagatee)
959            .insert(ChildOf(propagator));
960        app.update();
961
962        assert_eq!(
963            query.get_many(app.world(), [propagator, propagatee]),
964            Ok([&TestValue(1), &TestValue(2)])
965        );
966    }
967}