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