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};
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, Clone)]
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, Clone)]
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    sources: Query<(), With<Propagate<C>>>,
165) {
166    for (entity, source) in &changed {
167        commands
168            .entity(entity)
169            .try_insert(Inherited(source.0.clone()));
170    }
171
172    // set `Inherited::<C>` based on ancestry when `Propagate::<C>` is removed
173    for removed in removed.read() {
174        if !sources.contains(removed)
175            && let Ok(mut commands) = commands.get_entity(removed)
176        {
177            if let Some(inherited) = relationship
178                .get(removed)
179                .ok()
180                .and_then(|r| relations.get(r.get()).ok())
181            {
182                commands.insert(inherited.clone());
183            } else {
184                commands.try_remove::<Inherited<C>>();
185            }
186        }
187    }
188}
189
190/// Add/remove [`Inherited::<C>`] when an entity gains or changes its `R` relationship
191pub fn on_r_inserted<
192    C: Component + Clone + PartialEq,
193    F: QueryFilter + 'static,
194    R: Relationship,
195>(
196    event: On<Insert, R>,
197    mut commands: Commands,
198    query: Query<(&R, Has<Inherited<C>>), (Without<Propagate<C>>, F)>,
199    relations: Query<&Inherited<C>, Without<PropagateStop<C>>>,
200) {
201    let Ok((relation, has_inherited)) = query.get(event.entity) else {
202        return;
203    };
204    if let Ok(inherited) = relations.get(relation.get()) {
205        commands.entity(event.entity).try_insert(inherited.clone());
206    } else if has_inherited {
207        commands.entity(event.entity).try_remove::<Inherited<C>>();
208    }
209}
210
211/// Remove [`Inherited::<C>`] when an entity loses its `R` relationship
212pub fn on_r_removed<C: Component + Clone + PartialEq, F: QueryFilter + 'static, R: Relationship>(
213    event: On<Remove, R>,
214    mut commands: Commands,
215    query: Query<(), (With<Inherited<C>>, Without<Propagate<C>>, F)>,
216) {
217    if query.contains(event.entity) {
218        commands.entity(event.entity).try_remove::<Inherited<C>>();
219    }
220}
221
222/// When `PropagateOver` or `PropagateStop` is removed, update the `Inherited::<C>` to trigger propagation
223pub fn update_removed_limit<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
224    mut inherited: Query<&mut Inherited<C>>,
225    mut removed_skip: RemovedComponents<PropagateOver<C>>,
226    mut removed_stop: RemovedComponents<PropagateStop<C>>,
227) {
228    for entity in removed_skip.read() {
229        if let Ok(mut inherited) = inherited.get_mut(entity) {
230            inherited.set_changed();
231        }
232    }
233    for entity in removed_stop.read() {
234        if let Ok(mut inherited) = inherited.get_mut(entity) {
235            inherited.set_changed();
236        }
237    }
238}
239
240/// add/remove `Inherited::<C>` for targets of entities with modified `Inherited::<C>`
241pub fn propagate_inherited<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
242    mut commands: Commands,
243    changed: Query<
244        (&Inherited<C>, &R::RelationshipTarget),
245        (Changed<Inherited<C>>, Without<PropagateStop<C>>, F),
246    >,
247    recurse: Query<
248        (
249            Option<&R::RelationshipTarget>,
250            Option<&Inherited<C>>,
251            Option<&PropagateStop<C>>,
252        ),
253        (Without<Propagate<C>>, F),
254    >,
255    mut removed: RemovedComponents<Inherited<C>>,
256    mut to_process: Local<Vec<(Entity, Option<Inherited<C>>)>>,
257) {
258    // gather changed
259    for (inherited, targets) in &changed {
260        to_process.extend(
261            targets
262                .iter()
263                .map(|target| (target, Some(inherited.clone()))),
264        );
265    }
266
267    // and removed
268    for entity in removed.read() {
269        if let Ok((Some(targets), None, _)) = recurse.get(entity) {
270            to_process.extend(targets.iter().map(|target| (target, None)));
271        }
272    }
273
274    // propagate
275    while let Some((entity, maybe_inherited)) = (*to_process).pop() {
276        let Ok((maybe_targets, maybe_current, maybe_stop)) = recurse.get(entity) else {
277            continue;
278        };
279
280        if maybe_current == maybe_inherited.as_ref() {
281            continue;
282        }
283
284        // update children if required
285        if maybe_stop.is_none()
286            && let Some(targets) = maybe_targets
287        {
288            to_process.extend(
289                targets
290                    .iter()
291                    .map(|target| (target, maybe_inherited.clone())),
292            );
293        }
294
295        // update this node's `Inherited<C>`
296        if let Some(inherited) = maybe_inherited {
297            commands.entity(entity).try_insert(inherited);
298        } else {
299            commands.entity(entity).try_remove::<Inherited<C>>();
300        }
301    }
302}
303
304/// add/remove `C` on entities with `Inherited::<C>`
305pub fn propagate_output<C: Component + Clone + PartialEq, F: QueryFilter>(
306    mut commands: Commands,
307    changed: Query<
308        (Entity, &Inherited<C>, Option<&C>),
309        (Changed<Inherited<C>>, Without<PropagateOver<C>>, F),
310    >,
311    mut inherited_removed: RemovedComponents<Inherited<C>>,
312    without_propagation_components: Query<(), (Without<PropagateOver<C>>, Without<Inherited<C>>)>,
313) {
314    for (entity, inherited, maybe_current) in &changed {
315        if maybe_current.is_some_and(|c| &inherited.0 == c) {
316            continue;
317        }
318
319        commands.entity(entity).try_insert(inherited.0.clone());
320    }
321
322    for inherited_removed in inherited_removed.read() {
323        // Skip removal if propagation components were re-added this update
324        if without_propagation_components.contains(inherited_removed) {
325            commands.entity(inherited_removed).try_remove::<C>();
326        }
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use bevy_ecs::schedule::Schedule;
333
334    use crate::{App, Update};
335
336    use super::*;
337
338    #[derive(Component, Clone, PartialEq, Debug)]
339    struct TestValue(u32);
340
341    #[test]
342    fn test_simple_propagate() {
343        let mut app = App::new();
344        app.add_schedule(Schedule::new(Update));
345        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
346
347        let mut query = app.world_mut().query::<&TestValue>();
348
349        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
350        let intermediate = app
351            .world_mut()
352            .spawn_empty()
353            .insert(ChildOf(propagator))
354            .id();
355        let propagatee = app
356            .world_mut()
357            .spawn_empty()
358            .insert(ChildOf(intermediate))
359            .id();
360
361        app.update();
362
363        assert_eq!(
364            query.get_many(app.world(), [propagator, intermediate, propagatee]),
365            Ok([&TestValue(1), &TestValue(1), &TestValue(1)])
366        );
367    }
368
369    #[test]
370    fn test_remove_propagate() {
371        let mut app = App::new();
372        app.add_schedule(Schedule::new(Update));
373        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
374
375        let mut query = app.world_mut().query::<&TestValue>();
376
377        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
378        let propagatee = app
379            .world_mut()
380            .spawn_empty()
381            .insert(ChildOf(propagator))
382            .id();
383
384        app.update();
385
386        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
387
388        app.world_mut()
389            .commands()
390            .entity(propagator)
391            .remove::<Propagate<TestValue>>();
392        app.update();
393
394        assert!(query.get(app.world(), propagator).is_err());
395        assert!(query.get(app.world(), propagatee).is_err());
396    }
397
398    #[test]
399    fn test_remove_and_reinsert_propagate() {
400        let mut app = App::new();
401        app.add_schedule(Schedule::new(Update));
402        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
403
404        let parent = app.world_mut().spawn(Propagate(TestValue(1))).id();
405        let child = app.world_mut().spawn_empty().insert(ChildOf(parent)).id();
406
407        app.update();
408
409        app.world_mut()
410            .entity_mut(parent)
411            .remove::<Propagate<TestValue>>()
412            .insert(Propagate(TestValue(2)));
413
414        app.update();
415
416        assert_eq!(
417            app.world_mut()
418                .query::<&TestValue>()
419                .get_many(app.world(), [parent, child]),
420            Ok([&TestValue(2), &TestValue(2)])
421        );
422    }
423
424    #[test]
425    fn test_remove_orphan() {
426        let mut app = App::new();
427        app.add_schedule(Schedule::new(Update));
428        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
429
430        let mut query = app.world_mut().query::<&TestValue>();
431
432        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
433        let propagatee = app
434            .world_mut()
435            .spawn_empty()
436            .insert(ChildOf(propagator))
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            .remove::<ChildOf>();
447        app.update();
448
449        assert!(query.get(app.world(), propagatee).is_err());
450    }
451
452    #[test]
453    fn test_reparented() {
454        let mut app = App::new();
455        app.add_schedule(Schedule::new(Update));
456        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
457
458        let mut query = app.world_mut().query::<&TestValue>();
459
460        let propagator_one = app.world_mut().spawn(Propagate(TestValue(1))).id();
461        let other_parent = app.world_mut().spawn_empty().id();
462        let propagatee = app
463            .world_mut()
464            .spawn_empty()
465            .insert(ChildOf(propagator_one))
466            .id();
467
468        app.update();
469
470        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
471
472        app.world_mut()
473            .commands()
474            .entity(propagatee)
475            .insert(ChildOf(other_parent));
476
477        app.update();
478
479        assert!(query.get(app.world(), propagatee).is_err());
480    }
481
482    #[test]
483    fn test_reparented_with_prior() {
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_a = app.world_mut().spawn(Propagate(TestValue(1))).id();
491        let propagator_b = app.world_mut().spawn(Propagate(TestValue(2))).id();
492        let propagatee = app
493            .world_mut()
494            .spawn_empty()
495            .insert(ChildOf(propagator_a))
496            .id();
497
498        app.update();
499
500        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
501
502        app.world_mut()
503            .commands()
504            .entity(propagatee)
505            .insert(ChildOf(propagator_b));
506        app.update();
507
508        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(2)));
509    }
510
511    #[test]
512    fn test_detach_and_reattach_propagates_to_descendants() {
513        let mut app = App::new();
514        app.add_schedule(Schedule::new(Update));
515        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
516
517        let mut query = app.world_mut().query::<&TestValue>();
518
519        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
520        let intermediate = app
521            .world_mut()
522            .spawn_empty()
523            .insert(ChildOf(propagator))
524            .id();
525        let propagatee = app
526            .world_mut()
527            .spawn_empty()
528            .insert(ChildOf(intermediate))
529            .id();
530
531        app.update();
532
533        assert_eq!(
534            query.get_many(app.world(), [intermediate, propagatee]),
535            Ok([&TestValue(1), &TestValue(1)])
536        );
537
538        app.world_mut()
539            .entity_mut(intermediate)
540            .remove::<ChildOf>()
541            .insert(ChildOf(propagator));
542        app.update();
543
544        assert_eq!(
545            query.get_many(app.world(), [intermediate, propagatee]),
546            Ok([&TestValue(1), &TestValue(1)])
547        );
548    }
549
550    #[test]
551    fn test_propagate_over() {
552        let mut app = App::new();
553        app.add_schedule(Schedule::new(Update));
554        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
555
556        let mut query = app.world_mut().query::<&TestValue>();
557
558        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
559        let propagate_over = app
560            .world_mut()
561            .spawn(TestValue(2))
562            .insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
563            .id();
564        let propagatee = app
565            .world_mut()
566            .spawn_empty()
567            .insert(ChildOf(propagate_over))
568            .id();
569
570        app.update();
571
572        assert_eq!(query.get(app.world(), propagate_over), Ok(&TestValue(2)));
573        assert_eq!(query.get(app.world(), propagatee), Ok(&TestValue(1)));
574    }
575
576    #[test]
577    fn test_remove_propagate_over() {
578        let mut app = App::new();
579        app.add_schedule(Schedule::new(Update));
580        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
581
582        let mut query = app.world_mut().query::<&TestValue>();
583
584        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
585        let propagate_over = app
586            .world_mut()
587            .spawn(TestValue(2))
588            .insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
589            .id();
590        let propagatee = app
591            .world_mut()
592            .spawn_empty()
593            .insert(ChildOf(propagate_over))
594            .id();
595
596        app.update();
597        assert_eq!(
598            app.world_mut()
599                .query::<&Inherited<TestValue>>()
600                .get(app.world(), propagate_over),
601            Ok(&Inherited(TestValue(1)))
602        );
603        assert_eq!(
604            app.world_mut()
605                .query::<&Inherited<TestValue>>()
606                .get(app.world(), propagatee),
607            Ok(&Inherited(TestValue(1)))
608        );
609        assert_eq!(
610            query.get_many(app.world(), [propagate_over, propagatee]),
611            Ok([&TestValue(2), &TestValue(1)])
612        );
613
614        app.world_mut()
615            .commands()
616            .entity(propagate_over)
617            .remove::<PropagateOver<TestValue>>();
618        app.update();
619
620        assert_eq!(
621            query.get_many(app.world(), [propagate_over, propagatee]),
622            Ok([&TestValue(1), &TestValue(1)])
623        );
624    }
625
626    #[test]
627    fn test_propagate_over_parent_removed() {
628        let mut app = App::new();
629        app.add_schedule(Schedule::new(Update));
630        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
631
632        let mut query = app.world_mut().query::<&TestValue>();
633
634        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
635        let propagate_over = app
636            .world_mut()
637            .spawn(TestValue(2))
638            .insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
639            .id();
640
641        app.update();
642
643        assert_eq!(
644            query.get_many(app.world(), [propagator, propagate_over]),
645            Ok([&TestValue(1), &TestValue(2)])
646        );
647
648        app.world_mut()
649            .commands()
650            .entity(propagator)
651            .remove::<Propagate<TestValue>>();
652        app.update();
653
654        assert!(query.get(app.world(), propagator).is_err(),);
655        assert_eq!(query.get(app.world(), propagate_over), Ok(&TestValue(2)));
656    }
657
658    #[test]
659    fn test_orphaned_propagate_over() {
660        let mut app = App::new();
661        app.add_schedule(Schedule::new(Update));
662        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
663
664        let mut query = app.world_mut().query::<&TestValue>();
665
666        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
667        let propagate_over = app
668            .world_mut()
669            .spawn(TestValue(2))
670            .insert((PropagateOver::<TestValue>::default(), ChildOf(propagator)))
671            .id();
672        let propagatee = app
673            .world_mut()
674            .spawn_empty()
675            .insert(ChildOf(propagate_over))
676            .id();
677
678        app.update();
679
680        assert_eq!(
681            query.get_many(app.world(), [propagate_over, propagatee]),
682            Ok([&TestValue(2), &TestValue(1)])
683        );
684
685        app.world_mut()
686            .commands()
687            .entity(propagate_over)
688            .remove::<ChildOf>();
689        app.update();
690
691        assert_eq!(query.get(app.world(), propagate_over), Ok(&TestValue(2)));
692        assert!(query.get(app.world(), propagatee).is_err());
693    }
694
695    #[test]
696    fn test_propagate_stop() {
697        let mut app = App::new();
698        app.add_schedule(Schedule::new(Update));
699        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
700
701        let mut query = app.world_mut().query::<&TestValue>();
702
703        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
704        let propagate_stop = app
705            .world_mut()
706            .spawn(PropagateStop::<TestValue>::default())
707            .insert(ChildOf(propagator))
708            .id();
709        let no_propagatee = app
710            .world_mut()
711            .spawn_empty()
712            .insert(ChildOf(propagate_stop))
713            .id();
714
715        app.update();
716
717        assert_eq!(query.get(app.world(), propagate_stop), Ok(&TestValue(1)));
718        assert!(query.get(app.world(), no_propagatee).is_err());
719    }
720
721    #[test]
722    fn test_remove_propagate_stop() {
723        let mut app = App::new();
724        app.add_schedule(Schedule::new(Update));
725        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
726
727        let mut query = app.world_mut().query::<&TestValue>();
728
729        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
730        let propagate_stop = app
731            .world_mut()
732            .spawn(PropagateStop::<TestValue>::default())
733            .insert(ChildOf(propagator))
734            .id();
735        let no_propagatee = app
736            .world_mut()
737            .spawn_empty()
738            .insert(ChildOf(propagate_stop))
739            .id();
740
741        app.update();
742
743        assert_eq!(query.get(app.world(), propagate_stop), Ok(&TestValue(1)));
744        assert!(query.get(app.world(), no_propagatee).is_err());
745
746        app.world_mut()
747            .commands()
748            .entity(propagate_stop)
749            .remove::<PropagateStop<TestValue>>();
750        app.update();
751
752        assert_eq!(
753            query.get_many(app.world(), [propagate_stop, no_propagatee]),
754            Ok([&TestValue(1), &TestValue(1)])
755        );
756    }
757
758    #[test]
759    fn test_intermediate_override() {
760        let mut app = App::new();
761        app.add_schedule(Schedule::new(Update));
762        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
763
764        let mut query = app.world_mut().query::<&TestValue>();
765
766        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
767        let intermediate = app
768            .world_mut()
769            .spawn_empty()
770            .insert(ChildOf(propagator))
771            .id();
772        let propagatee = app
773            .world_mut()
774            .spawn_empty()
775            .insert(ChildOf(intermediate))
776            .id();
777
778        app.update();
779
780        assert_eq!(
781            query.get_many(app.world(), [propagator, intermediate, propagatee]),
782            Ok([&TestValue(1), &TestValue(1), &TestValue(1)])
783        );
784
785        app.world_mut()
786            .entity_mut(intermediate)
787            .insert(Propagate(TestValue(2)));
788        app.update();
789
790        assert_eq!(
791            app.world_mut()
792                .query::<&TestValue>()
793                .get_many(app.world(), [propagator, intermediate, propagatee]),
794            Ok([&TestValue(1), &TestValue(2), &TestValue(2)])
795        );
796    }
797
798    #[test]
799    fn test_filter() {
800        #[derive(Component)]
801        struct Marker;
802
803        let mut app = App::new();
804        app.add_schedule(Schedule::new(Update));
805        app.add_plugins(HierarchyPropagatePlugin::<TestValue, With<Marker>>::new(
806            Update,
807        ));
808
809        let mut query = app.world_mut().query::<&TestValue>();
810
811        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
812        let propagatee = app
813            .world_mut()
814            .spawn_empty()
815            .insert(ChildOf(propagator))
816            .id();
817
818        app.update();
819
820        assert!(query.get(app.world(), propagator).is_err());
821        assert!(query.get(app.world(), propagatee).is_err());
822
823        // NOTE: changes to the filter condition are not rechecked
824        app.world_mut().entity_mut(propagator).insert(Marker);
825        app.update();
826
827        assert!(query.get(app.world(), propagator).is_err());
828        assert!(query.get(app.world(), propagatee).is_err());
829
830        app.world_mut()
831            .entity_mut(propagator)
832            .insert(Propagate(TestValue(1)));
833        app.update();
834
835        assert_eq!(query.get(app.world(), propagator), Ok(&TestValue(1)));
836        assert!(query.get(app.world(), propagatee).is_err());
837
838        app.world_mut().entity_mut(propagatee).insert(Marker);
839        app.update();
840
841        assert_eq!(query.get(app.world(), propagator), Ok(&TestValue(1)));
842        assert!(query.get(app.world(), propagatee).is_err());
843
844        app.world_mut()
845            .entity_mut(propagator)
846            .insert(Propagate(TestValue(1)));
847        app.update();
848
849        assert_eq!(
850            app.world_mut()
851                .query::<&TestValue>()
852                .get_many(app.world(), [propagator, propagatee]),
853            Ok([&TestValue(1), &TestValue(1)])
854        );
855    }
856
857    #[test]
858    fn test_removed_propagate_still_inherits() {
859        let mut app = App::new();
860        app.add_schedule(Schedule::new(Update));
861        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
862
863        let mut query = app.world_mut().query::<&TestValue>();
864
865        let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
866        let propagatee = app
867            .world_mut()
868            .spawn(Propagate(TestValue(2)))
869            .insert(ChildOf(propagator))
870            .id();
871
872        app.update();
873
874        assert_eq!(
875            query.get_many(app.world(), [propagator, propagatee]),
876            Ok([&TestValue(1), &TestValue(2)])
877        );
878
879        app.world_mut()
880            .commands()
881            .entity(propagatee)
882            .remove::<Propagate<TestValue>>();
883        app.update();
884
885        assert_eq!(
886            query.get_many(app.world(), [propagator, propagatee]),
887            Ok([&TestValue(1), &TestValue(1)])
888        );
889    }
890
891    #[test]
892    fn test_reparent_respects_stop() {
893        let mut app = App::new();
894        app.add_schedule(Schedule::new(Update));
895        app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
896
897        let mut query = app.world_mut().query::<&TestValue>();
898
899        let propagator = app
900            .world_mut()
901            .spawn((
902                Propagate(TestValue(1)),
903                PropagateStop::<TestValue>::default(),
904            ))
905            .id();
906        let propagatee = app.world_mut().spawn(TestValue(2)).id();
907
908        app.update();
909
910        assert_eq!(
911            query.get_many(app.world(), [propagator, propagatee]),
912            Ok([&TestValue(1), &TestValue(2)])
913        );
914
915        app.world_mut()
916            .commands()
917            .entity(propagatee)
918            .insert(ChildOf(propagator));
919        app.update();
920
921        assert_eq!(
922            query.get_many(app.world(), [propagator, propagatee]),
923            Ok([&TestValue(1), &TestValue(2)])
924        );
925    }
926}