Skip to content

Commit 7d0edbc

Browse files
committed
Improve change detection behavior for transform propagation (#6870)
# Objective Fix #4647. If any child is changed, or even reordered, `Changed<Children>` is true, which causes transform propagation to propagate changes to all siblings of a changed child, even if they don't need to be. ## Solution As `Parent` and `Children` are updated in tandem in hierarchy commands after #4800. `Changed<Parent>` is true on the child when `Changed<Children>` is true on the parent. However, unlike checking children, checking `Changed<Parent>` is only localized to the current entity and will not force propagation to the siblings. Also took the opportunity to change propagation to use `Query::iter_many` instead of repeated `Query::get` calls. Should cut a bit of the overhead out of propagation. This means we won't panic when there isn't a `Parent` on the child, just skip over it. The tests from #4608 still pass, so the change detection here still works just fine under this approach.
1 parent 0ca9c61 commit 7d0edbc

File tree

1 file changed

+43
-47
lines changed

1 file changed

+43
-47
lines changed

crates/bevy_transform/src/systems.rs

Lines changed: 43 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,33 @@ pub fn sync_simple_transforms(
2727
/// to propagate transforms correctly.
2828
pub fn propagate_transforms(
2929
mut root_query: Query<
30-
(Entity, Ref<Children>, Ref<Transform>, &mut GlobalTransform),
30+
(Entity, &Children, Ref<Transform>, &mut GlobalTransform),
3131
Without<Parent>,
3232
>,
33-
transform_query: Query<(Ref<Transform>, &mut GlobalTransform), With<Parent>>,
34-
parent_query: Query<&Parent>,
35-
children_query: Query<Ref<Children>, (With<Parent>, With<GlobalTransform>)>,
33+
transform_query: Query<(Ref<Transform>, &mut GlobalTransform, Option<&Children>), With<Parent>>,
34+
parent_query: Query<(Entity, Ref<Parent>)>,
3635
) {
3736
root_query.par_for_each_mut(
3837
// The differing depths and sizes of hierarchy trees causes the work for each root to be
3938
// different. A batch size of 1 ensures that each tree gets it's own task and multiple
4039
// large trees are not clumped together.
4140
1,
4241
|(entity, children, transform, mut global_transform)| {
43-
let mut changed = transform.is_changed();
42+
let changed = transform.is_changed();
4443
if changed {
4544
*global_transform = GlobalTransform::from(*transform);
4645
}
4746

48-
// If our `Children` has changed, we need to recalculate everything below us
49-
changed |= children.is_changed();
50-
51-
for child in children.iter() {
47+
for (child, actual_parent) in parent_query.iter_many(children) {
48+
assert_eq!(
49+
actual_parent.get(), entity,
50+
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
51+
);
5252
// SAFETY:
53-
// - We may operate as if the hierarchy is consistent, since `propagate_recursive` will panic before continuing
54-
// to propagate if it encounters an entity with inconsistent parentage.
53+
// - `child` must have consistent parentage, or the above assertion would panic.
54+
// Since `child` is parented to a root entity, the entire hierarchy leading to it is consistent.
55+
// - We may operate as if all descendants are consistent, since `propagate_recursive` will panic before
56+
// continuing to propagate if it encounters an entity with inconsistent parentage.
5557
// - Since each root entity is unique and the hierarchy is consistent and forest-like,
5658
// other root entities' `propagate_recursive` calls will not conflict with this one.
5759
// - Since this is the only place where `transform_query` gets used, there will be no conflicting fetches elsewhere.
@@ -60,10 +62,8 @@ pub fn propagate_transforms(
6062
&global_transform,
6163
&transform_query,
6264
&parent_query,
63-
&children_query,
64-
entity,
65-
*child,
66-
changed,
65+
child,
66+
changed || actual_parent.is_changed(),
6767
);
6868
}
6969
}
@@ -75,35 +75,30 @@ pub fn propagate_transforms(
7575
///
7676
/// # Panics
7777
///
78-
/// If `entity` or any of its descendants have a malformed hierarchy.
79-
/// The panic will occur before propagating the transforms of any malformed entities and their descendants.
78+
/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before propagating
79+
/// the transforms of any malformed entities and their descendants.
8080
///
8181
/// # Safety
8282
///
83-
/// While this function is running, `unsafe_transform_query` must not have any fetches for `entity`,
83+
/// - While this function is running, `transform_query` must not have any fetches for `entity`,
8484
/// nor any of its descendants.
85+
/// - The caller must ensure that the hierarchy leading to `entity`
86+
/// is well-formed and must remain as a tree or a forest. Each entity must have at most one parent.
8587
unsafe fn propagate_recursive(
8688
parent: &GlobalTransform,
87-
unsafe_transform_query: &Query<(Ref<Transform>, &mut GlobalTransform), With<Parent>>,
88-
parent_query: &Query<&Parent>,
89-
children_query: &Query<Ref<Children>, (With<Parent>, With<GlobalTransform>)>,
90-
expected_parent: Entity,
89+
transform_query: &Query<
90+
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
91+
With<Parent>,
92+
>,
93+
parent_query: &Query<(Entity, Ref<Parent>)>,
9194
entity: Entity,
9295
mut changed: bool,
9396
) {
94-
let Ok(actual_parent) = parent_query.get(entity) else {
95-
panic!("Propagated child for {entity:?} has no Parent component!");
96-
};
97-
assert_eq!(
98-
actual_parent.get(), expected_parent,
99-
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
100-
);
101-
102-
let global_matrix = {
103-
let Ok((transform, mut global_transform)) =
97+
let (global_matrix, children) = {
98+
let Ok((transform, mut global_transform, children)) =
10499
// SAFETY: This call cannot create aliased mutable references.
105100
// - The top level iteration parallelizes on the roots of the hierarchy.
106-
// - The above assertion ensures that each child has one and only one unique parent throughout the entire
101+
// - The caller ensures that each child has one and only one unique parent throughout the entire
107102
// hierarchy.
108103
//
109104
// For example, consider the following malformed hierarchy:
@@ -127,34 +122,35 @@ unsafe fn propagate_recursive(
127122
//
128123
// Even if these A and B start two separate tasks running in parallel, one of them will panic before attempting
129124
// to mutably access E.
130-
(unsafe { unsafe_transform_query.get_unchecked(entity) }) else {
125+
(unsafe { transform_query.get_unchecked(entity) }) else {
131126
return;
132127
};
133128

134129
changed |= transform.is_changed();
135130
if changed {
136131
*global_transform = parent.mul_transform(*transform);
137132
}
138-
*global_transform
133+
(*global_transform, children)
139134
};
140135

141-
let Ok(children) = children_query.get(entity) else {
142-
return
143-
};
144-
// If our `Children` has changed, we need to recalculate everything below us
145-
changed |= children.is_changed();
146-
for child in &children {
147-
// SAFETY: The caller guarantees that `unsafe_transform_query` will not be fetched
136+
let Some(children) = children else { return };
137+
for (child, actual_parent) in parent_query.iter_many(children) {
138+
assert_eq!(
139+
actual_parent.get(), entity,
140+
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
141+
);
142+
// SAFETY: The caller guarantees that `transform_query` will not be fetched
148143
// for any descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
144+
//
145+
// The above assertion ensures that each child has one and only one unique parent throughout the
146+
// entire hierarchy.
149147
unsafe {
150148
propagate_recursive(
151149
&global_matrix,
152-
unsafe_transform_query,
150+
transform_query,
153151
parent_query,
154-
children_query,
155-
entity,
156-
*child,
157-
changed,
152+
child,
153+
changed || actual_parent.is_changed(),
158154
);
159155
}
160156
}

0 commit comments

Comments
 (0)