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