Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_app/src/propagate.rs
6595 views
1
use alloc::vec::Vec;
2
use core::marker::PhantomData;
3
4
use crate::{App, Plugin};
5
#[cfg(feature = "bevy_reflect")]
6
use bevy_ecs::reflect::ReflectComponent;
7
use 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")]
19
use bevy_reflect::Reflect;
20
21
/// Plugin to automatically propagate a component value to all direct and transient relationship
22
/// targets (e.g. [`bevy_ecs::hierarchy::Children`]) of entities with a [`Propagate`] component.
23
///
24
/// The plugin Will maintain the target component over hierarchy changes, adding or removing
25
/// `C` when a relationship `R` (e.g. [`ChildOf`]) is added to or removed from a
26
/// relationship tree with a [`Propagate<C>`] source, or if the [`Propagate<C>`] component
27
/// is added, changed or removed.
28
///
29
/// Optionally you can include a query filter `F` to restrict the entities that are updated.
30
/// Note that the filter is not rechecked dynamically: changes to the filter state will not be
31
/// picked up until the [`Propagate`] component is touched, or the hierarchy is changed.
32
/// All members of the tree between source and target must match the filter for propagation
33
/// to reach a given target.
34
/// Individual entities can be skipped or terminate the propagation with the [`PropagateOver`]
35
/// and [`PropagateStop`] components.
36
///
37
/// The schedule can be configured via [`HierarchyPropagatePlugin::new`].
38
/// You should be sure to schedule your logic relative to this set: making changes
39
/// that modify component values before this logic, and reading the propagated
40
/// values after it.
41
pub 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
50
impl<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>
51
HierarchyPropagatePlugin<C, F, R>
52
{
53
/// Construct the plugin. The propagation systems will be placed in the specified schedule.
54
pub fn new(schedule: impl ScheduleLabel) -> Self {
55
Self {
56
schedule: schedule.intern(),
57
_marker: PhantomData,
58
}
59
}
60
}
61
62
/// Causes the inner component to be added to this entity and all direct and transient relationship
63
/// targets. A target with a [`Propagate<C>`] component of its own will override propagation from
64
/// that point in the tree.
65
#[derive(Component, Clone, PartialEq)]
66
#[cfg_attr(
67
feature = "bevy_reflect",
68
derive(Reflect),
69
reflect(Component, Clone, PartialEq)
70
)]
71
pub struct Propagate<C: Component + Clone + PartialEq>(pub C);
72
73
/// Stops the output component being added to this entity.
74
/// Relationship targets will still inherit the component from this entity or its parents.
75
#[derive(Component)]
76
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
77
pub struct PropagateOver<C>(PhantomData<fn() -> C>);
78
79
/// Stops the propagation at this entity. Children will not inherit the component.
80
#[derive(Component)]
81
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
82
pub struct PropagateStop<C>(PhantomData<fn() -> C>);
83
84
/// The set in which propagation systems are added. You can schedule your logic relative to this set.
85
#[derive(SystemSet, Clone, PartialEq, PartialOrd, Ord)]
86
pub struct PropagateSet<C: Component + Clone + PartialEq> {
87
_p: PhantomData<fn() -> C>,
88
}
89
90
/// Internal struct for managing propagation
91
#[derive(Component, Clone, PartialEq)]
92
#[cfg_attr(
93
feature = "bevy_reflect",
94
derive(Reflect),
95
reflect(Component, Clone, PartialEq)
96
)]
97
pub struct Inherited<C: Component + Clone + PartialEq>(pub C);
98
99
impl<C> Default for PropagateOver<C> {
100
fn default() -> Self {
101
Self(Default::default())
102
}
103
}
104
105
impl<C> Default for PropagateStop<C> {
106
fn default() -> Self {
107
Self(Default::default())
108
}
109
}
110
111
impl<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
119
impl<C: Component + Clone + PartialEq> Eq for PropagateSet<C> {}
120
121
impl<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
127
impl<C: Component + Clone + PartialEq> Default for PropagateSet<C> {
128
fn default() -> Self {
129
Self {
130
_p: Default::default(),
131
}
132
}
133
}
134
135
impl<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
154
/// add/remove `Inherited::<C>` and `C` for entities with a direct `Propagate::<C>`
155
pub 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
179
/// remove `Inherited::<C>` and `C` for entities with a `PropagateStop::<C>`
180
pub 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
190
/// add/remove `Inherited::<C>` and `C` for entities which have changed relationship
191
pub 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
218
/// add/remove `Inherited::<C>` for targets of entities with modified `Inherited::<C>`
219
pub 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
// gather changed
233
for (inherited, targets) in &changed {
234
to_process.extend(
235
targets
236
.iter()
237
.map(|target| (target, Some(inherited.clone()))),
238
);
239
}
240
241
// and removed
242
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
// propagate
249
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
274
/// add `C` to entities with `Inherited::<C>`
275
pub 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)]
292
mod 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
// NOTE: changes to the filter condition are not rechecked
564
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
}
584
585