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 component::Component,
9 entity::Entity,
10 hierarchy::ChildOf,
11 intern::Interned,
12 lifecycle::RemovedComponents,
13 query::{Changed, Or, QueryFilter, With, Without},
14 relationship::{Relationship, RelationshipTarget},
15 schedule::{IntoScheduleConfigs, ScheduleLabel, SystemSet},
16 system::{Commands, Local, Query},
17};
18#[cfg(feature = "bevy_reflect")]
19use bevy_reflect::Reflect;
20
21pub struct HierarchyPropagatePlugin<
42 C: Component + Clone + PartialEq,
43 F: QueryFilter = (),
44 R: Relationship = ChildOf,
45> {
46 schedule: Interned<dyn ScheduleLabel>,
47 _marker: PhantomData<fn() -> (C, F, R)>,
48}
49
50impl<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>
51 HierarchyPropagatePlugin<C, F, R>
52{
53 pub fn new(schedule: impl ScheduleLabel) -> Self {
55 Self {
56 schedule: schedule.intern(),
57 _marker: PhantomData,
58 }
59 }
60}
61
62#[derive(Component, Clone, PartialEq)]
66#[cfg_attr(
67 feature = "bevy_reflect",
68 derive(Reflect),
69 reflect(Component, Clone, PartialEq)
70)]
71pub struct Propagate<C: Component + Clone + PartialEq>(pub C);
72
73#[derive(Component)]
76#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
77pub struct PropagateOver<C>(PhantomData<fn() -> C>);
78
79#[derive(Component)]
81#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
82pub struct PropagateStop<C>(PhantomData<fn() -> C>);
83
84#[derive(SystemSet, Clone, PartialEq, PartialOrd, Ord)]
86pub struct PropagateSet<C: Component + Clone + PartialEq> {
87 _p: PhantomData<fn() -> C>,
88}
89
90#[derive(Component, Clone, PartialEq)]
92#[cfg_attr(
93 feature = "bevy_reflect",
94 derive(Reflect),
95 reflect(Component, Clone, PartialEq)
96)]
97pub struct Inherited<C: Component + Clone + PartialEq>(pub C);
98
99impl<C> Default for PropagateOver<C> {
100 fn default() -> Self {
101 Self(Default::default())
102 }
103}
104
105impl<C> Default for PropagateStop<C> {
106 fn default() -> Self {
107 Self(Default::default())
108 }
109}
110
111impl<C: Component + Clone + PartialEq> core::fmt::Debug for PropagateSet<C> {
112 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
113 f.debug_struct("PropagateSet")
114 .field("_p", &self._p)
115 .finish()
116 }
117}
118
119impl<C: Component + Clone + PartialEq> Eq for PropagateSet<C> {}
120
121impl<C: Component + Clone + PartialEq> core::hash::Hash for PropagateSet<C> {
122 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
123 self._p.hash(state);
124 }
125}
126
127impl<C: Component + Clone + PartialEq> Default for PropagateSet<C> {
128 fn default() -> Self {
129 Self {
130 _p: Default::default(),
131 }
132 }
133}
134
135impl<C: Component + Clone + PartialEq, F: QueryFilter + 'static, R: Relationship> Plugin
136 for HierarchyPropagatePlugin<C, F, R>
137{
138 fn build(&self, app: &mut App) {
139 app.add_systems(
140 self.schedule,
141 (
142 update_source::<C, F>,
143 update_stopped::<C, F>,
144 update_reparented::<C, F, R>,
145 propagate_inherited::<C, F, R>,
146 propagate_output::<C, F>,
147 )
148 .chain()
149 .in_set(PropagateSet::<C>::default()),
150 );
151 }
152}
153
154pub fn update_source<C: Component + Clone + PartialEq, F: QueryFilter>(
156 mut commands: Commands,
157 changed: Query<
158 (Entity, &Propagate<C>),
159 (
160 Or<(Changed<Propagate<C>>, Without<Inherited<C>>)>,
161 Without<PropagateStop<C>>,
162 ),
163 >,
164 mut removed: RemovedComponents<Propagate<C>>,
165) {
166 for (entity, source) in &changed {
167 commands
168 .entity(entity)
169 .try_insert(Inherited(source.0.clone()));
170 }
171
172 for removed in removed.read() {
173 if let Ok(mut commands) = commands.get_entity(removed) {
174 commands.remove::<(Inherited<C>, C)>();
175 }
176 }
177}
178
179pub fn update_stopped<C: Component + Clone + PartialEq, F: QueryFilter>(
181 mut commands: Commands,
182 q: Query<Entity, (With<Inherited<C>>, With<PropagateStop<C>>, F)>,
183) {
184 for entity in q.iter() {
185 let mut cmds = commands.entity(entity);
186 cmds.remove::<(Inherited<C>, C)>();
187 }
188}
189
190pub fn update_reparented<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
192 mut commands: Commands,
193 moved: Query<
194 (Entity, &R, Option<&Inherited<C>>),
195 (
196 Changed<R>,
197 Without<Propagate<C>>,
198 Without<PropagateStop<C>>,
199 F,
200 ),
201 >,
202 relations: Query<&Inherited<C>>,
203 orphaned: Query<Entity, (With<Inherited<C>>, Without<Propagate<C>>, Without<R>, F)>,
204) {
205 for (entity, relation, maybe_inherited) in &moved {
206 if let Ok(inherited) = relations.get(relation.get()) {
207 commands.entity(entity).try_insert(inherited.clone());
208 } else if maybe_inherited.is_some() {
209 commands.entity(entity).remove::<(Inherited<C>, C)>();
210 }
211 }
212
213 for orphan in &orphaned {
214 commands.entity(orphan).remove::<(Inherited<C>, C)>();
215 }
216}
217
218pub fn propagate_inherited<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
220 mut commands: Commands,
221 changed: Query<
222 (&Inherited<C>, &R::RelationshipTarget),
223 (Changed<Inherited<C>>, Without<PropagateStop<C>>, F),
224 >,
225 recurse: Query<
226 (Option<&R::RelationshipTarget>, Option<&Inherited<C>>),
227 (Without<Propagate<C>>, Without<PropagateStop<C>>, F),
228 >,
229 mut removed: RemovedComponents<Inherited<C>>,
230 mut to_process: Local<Vec<(Entity, Option<Inherited<C>>)>>,
231) {
232 for (inherited, targets) in &changed {
234 to_process.extend(
235 targets
236 .iter()
237 .map(|target| (target, Some(inherited.clone()))),
238 );
239 }
240
241 for entity in removed.read() {
243 if let Ok((Some(targets), _)) = recurse.get(entity) {
244 to_process.extend(targets.iter().map(|target| (target, None)));
245 }
246 }
247
248 while let Some((entity, maybe_inherited)) = (*to_process).pop() {
250 let Ok((maybe_targets, maybe_current)) = recurse.get(entity) else {
251 continue;
252 };
253
254 if maybe_current == maybe_inherited.as_ref() {
255 continue;
256 }
257
258 if let Some(targets) = maybe_targets {
259 to_process.extend(
260 targets
261 .iter()
262 .map(|target| (target, maybe_inherited.clone())),
263 );
264 }
265
266 if let Some(inherited) = maybe_inherited {
267 commands.entity(entity).try_insert(inherited.clone());
268 } else {
269 commands.entity(entity).remove::<(Inherited<C>, C)>();
270 }
271 }
272}
273
274pub fn propagate_output<C: Component + Clone + PartialEq, F: QueryFilter>(
276 mut commands: Commands,
277 changed: Query<
278 (Entity, &Inherited<C>, Option<&C>),
279 (Changed<Inherited<C>>, Without<PropagateOver<C>>, F),
280 >,
281) {
282 for (entity, inherited, maybe_current) in &changed {
283 if maybe_current.is_some_and(|c| &inherited.0 == c) {
284 continue;
285 }
286
287 commands.entity(entity).try_insert(inherited.0.clone());
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use bevy_ecs::schedule::Schedule;
294
295 use crate::{App, Update};
296
297 use super::*;
298
299 #[derive(Component, Clone, PartialEq, Debug)]
300 struct TestValue(u32);
301
302 #[test]
303 fn test_simple_propagate() {
304 let mut app = App::new();
305 app.add_schedule(Schedule::new(Update));
306 app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
307
308 let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
309 let intermediate = app
310 .world_mut()
311 .spawn_empty()
312 .insert(ChildOf(propagator))
313 .id();
314 let propagatee = app
315 .world_mut()
316 .spawn_empty()
317 .insert(ChildOf(intermediate))
318 .id();
319
320 app.update();
321
322 assert!(app
323 .world_mut()
324 .query::<&TestValue>()
325 .get(app.world(), propagatee)
326 .is_ok());
327 }
328
329 #[test]
330 fn test_reparented() {
331 let mut app = App::new();
332 app.add_schedule(Schedule::new(Update));
333 app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
334
335 let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
336 let propagatee = app
337 .world_mut()
338 .spawn_empty()
339 .insert(ChildOf(propagator))
340 .id();
341
342 app.update();
343
344 assert!(app
345 .world_mut()
346 .query::<&TestValue>()
347 .get(app.world(), propagatee)
348 .is_ok());
349 }
350
351 #[test]
352 fn test_reparented_with_prior() {
353 let mut app = App::new();
354 app.add_schedule(Schedule::new(Update));
355 app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
356
357 let propagator_a = app.world_mut().spawn(Propagate(TestValue(1))).id();
358 let propagator_b = app.world_mut().spawn(Propagate(TestValue(2))).id();
359 let propagatee = app
360 .world_mut()
361 .spawn_empty()
362 .insert(ChildOf(propagator_a))
363 .id();
364
365 app.update();
366 assert_eq!(
367 app.world_mut()
368 .query::<&TestValue>()
369 .get(app.world(), propagatee),
370 Ok(&TestValue(1))
371 );
372 app.world_mut()
373 .commands()
374 .entity(propagatee)
375 .insert(ChildOf(propagator_b));
376 app.update();
377 assert_eq!(
378 app.world_mut()
379 .query::<&TestValue>()
380 .get(app.world(), propagatee),
381 Ok(&TestValue(2))
382 );
383 }
384
385 #[test]
386 fn test_remove_orphan() {
387 let mut app = App::new();
388 app.add_schedule(Schedule::new(Update));
389 app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
390
391 let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
392 let propagatee = app
393 .world_mut()
394 .spawn_empty()
395 .insert(ChildOf(propagator))
396 .id();
397
398 app.update();
399 assert!(app
400 .world_mut()
401 .query::<&TestValue>()
402 .get(app.world(), propagatee)
403 .is_ok());
404 app.world_mut()
405 .commands()
406 .entity(propagatee)
407 .remove::<ChildOf>();
408 app.update();
409 assert!(app
410 .world_mut()
411 .query::<&TestValue>()
412 .get(app.world(), propagatee)
413 .is_err());
414 }
415
416 #[test]
417 fn test_remove_propagated() {
418 let mut app = App::new();
419 app.add_schedule(Schedule::new(Update));
420 app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
421
422 let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
423 let propagatee = app
424 .world_mut()
425 .spawn_empty()
426 .insert(ChildOf(propagator))
427 .id();
428
429 app.update();
430 assert!(app
431 .world_mut()
432 .query::<&TestValue>()
433 .get(app.world(), propagatee)
434 .is_ok());
435 app.world_mut()
436 .commands()
437 .entity(propagator)
438 .remove::<Propagate<TestValue>>();
439 app.update();
440 assert!(app
441 .world_mut()
442 .query::<&TestValue>()
443 .get(app.world(), propagatee)
444 .is_err());
445 }
446
447 #[test]
448 fn test_propagate_over() {
449 let mut app = App::new();
450 app.add_schedule(Schedule::new(Update));
451 app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
452
453 let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
454 let propagate_over = app
455 .world_mut()
456 .spawn(TestValue(2))
457 .insert(ChildOf(propagator))
458 .id();
459 let propagatee = app
460 .world_mut()
461 .spawn_empty()
462 .insert(ChildOf(propagate_over))
463 .id();
464
465 app.update();
466 assert_eq!(
467 app.world_mut()
468 .query::<&TestValue>()
469 .get(app.world(), propagatee),
470 Ok(&TestValue(1))
471 );
472 }
473
474 #[test]
475 fn test_propagate_stop() {
476 let mut app = App::new();
477 app.add_schedule(Schedule::new(Update));
478 app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
479
480 let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
481 let propagate_stop = app
482 .world_mut()
483 .spawn(PropagateStop::<TestValue>::default())
484 .insert(ChildOf(propagator))
485 .id();
486 let no_propagatee = app
487 .world_mut()
488 .spawn_empty()
489 .insert(ChildOf(propagate_stop))
490 .id();
491
492 app.update();
493 assert!(app
494 .world_mut()
495 .query::<&TestValue>()
496 .get(app.world(), no_propagatee)
497 .is_err());
498 }
499
500 #[test]
501 fn test_intermediate_override() {
502 let mut app = App::new();
503 app.add_schedule(Schedule::new(Update));
504 app.add_plugins(HierarchyPropagatePlugin::<TestValue>::new(Update));
505
506 let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
507 let intermediate = app
508 .world_mut()
509 .spawn_empty()
510 .insert(ChildOf(propagator))
511 .id();
512 let propagatee = app
513 .world_mut()
514 .spawn_empty()
515 .insert(ChildOf(intermediate))
516 .id();
517
518 app.update();
519 assert_eq!(
520 app.world_mut()
521 .query::<&TestValue>()
522 .get(app.world(), propagatee),
523 Ok(&TestValue(1))
524 );
525
526 app.world_mut()
527 .entity_mut(intermediate)
528 .insert(Propagate(TestValue(2)));
529 app.update();
530 assert_eq!(
531 app.world_mut()
532 .query::<&TestValue>()
533 .get(app.world(), propagatee),
534 Ok(&TestValue(2))
535 );
536 }
537
538 #[test]
539 fn test_filter() {
540 #[derive(Component)]
541 struct Marker;
542
543 let mut app = App::new();
544 app.add_schedule(Schedule::new(Update));
545 app.add_plugins(HierarchyPropagatePlugin::<TestValue, With<Marker>>::new(
546 Update,
547 ));
548
549 let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
550 let propagatee = app
551 .world_mut()
552 .spawn_empty()
553 .insert(ChildOf(propagator))
554 .id();
555
556 app.update();
557 assert!(app
558 .world_mut()
559 .query::<&TestValue>()
560 .get(app.world(), propagatee)
561 .is_err());
562
563 app.world_mut().entity_mut(propagator).insert(Marker);
565 app.world_mut().entity_mut(propagatee).insert(Marker);
566 app.update();
567 assert!(app
568 .world_mut()
569 .query::<&TestValue>()
570 .get(app.world(), propagatee)
571 .is_err());
572
573 app.world_mut()
574 .entity_mut(propagator)
575 .insert(Propagate(TestValue(1)));
576 app.update();
577 assert!(app
578 .world_mut()
579 .query::<&TestValue>()
580 .get(app.world(), propagatee)
581 .is_ok());
582 }
583}