Skip to content

Commit 84c783b

Browse files
committed
Ensure that the parent is always the expected entity (#4717)
# Objective - Transform propogation could stack overflow when there was a cycle. - I think #4203 would use all available memory. ## Solution - Make sure that the child entity's `Parent`s are their parents. This is also required for when parallelising, although as noted in the comment, the naïve solution would be UB. (The best way to fix this would probably be an `&mut UnsafeCell<T>` `WorldQuery`, or wrapper type with the same effect)
1 parent 5422a2b commit 84c783b

File tree

1 file changed

+57
-11
lines changed

1 file changed

+57
-11
lines changed

crates/bevy_transform/src/systems.rs

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,21 @@ pub fn transform_propagate_system(
1111
&Transform,
1212
Changed<Transform>,
1313
&mut GlobalTransform,
14+
Entity,
1415
),
1516
Without<Parent>,
1617
>,
17-
mut transform_query: Query<
18-
(&Transform, Changed<Transform>, &mut GlobalTransform),
19-
With<Parent>,
20-
>,
18+
mut transform_query: Query<(
19+
&Transform,
20+
Changed<Transform>,
21+
&mut GlobalTransform,
22+
&Parent,
23+
)>,
2124
children_query: Query<(&Children, Changed<Children>), (With<Parent>, With<GlobalTransform>)>,
2225
) {
23-
for (children, transform, transform_changed, mut global_transform) in root_query.iter_mut() {
26+
for (children, transform, transform_changed, mut global_transform, entity) in
27+
root_query.iter_mut()
28+
{
2429
let mut changed = transform_changed;
2530
if transform_changed {
2631
*global_transform = GlobalTransform::from(*transform);
@@ -35,6 +40,7 @@ pub fn transform_propagate_system(
3540
&mut transform_query,
3641
&children_query,
3742
*child,
43+
entity,
3844
changed,
3945
);
4046
}
@@ -44,19 +50,26 @@ pub fn transform_propagate_system(
4450

4551
fn propagate_recursive(
4652
parent: &GlobalTransform,
47-
transform_query: &mut Query<
48-
(&Transform, Changed<Transform>, &mut GlobalTransform),
49-
With<Parent>,
50-
>,
53+
transform_query: &mut Query<(
54+
&Transform,
55+
Changed<Transform>,
56+
&mut GlobalTransform,
57+
&Parent,
58+
)>,
5159
children_query: &Query<(&Children, Changed<Children>), (With<Parent>, With<GlobalTransform>)>,
5260
entity: Entity,
61+
expected_parent: Entity,
5362
mut changed: bool,
5463
// We use a result here to use the `?` operator. Ideally we'd use a try block instead
5564
) -> Result<(), ()> {
5665
let global_matrix = {
57-
let (transform, transform_changed, mut global_transform) =
66+
let (transform, transform_changed, mut global_transform, child_parent) =
5867
transform_query.get_mut(entity).map_err(drop)?;
59-
68+
// Note that for parallelising, this check cannot occur here, since there is an `&mut GlobalTransform` (in global_transform)
69+
assert_eq!(
70+
child_parent.0, expected_parent,
71+
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
72+
);
6073
changed |= transform_changed;
6174
if changed {
6275
*global_transform = parent.mul_transform(*transform);
@@ -73,6 +86,7 @@ fn propagate_recursive(
7386
transform_query,
7487
children_query,
7588
*child,
89+
entity,
7690
changed,
7791
);
7892
}
@@ -322,4 +336,36 @@ mod test {
322336
);
323337
}
324338
}
339+
#[test]
340+
#[should_panic]
341+
fn panic_when_hierarchy_cycle() {
342+
let mut app = App::new();
343+
344+
app.add_system(parent_update_system);
345+
app.add_system(transform_propagate_system);
346+
347+
let child = app
348+
.world
349+
.spawn()
350+
.insert_bundle((Transform::identity(), GlobalTransform::default()))
351+
.id();
352+
353+
let grandchild = app
354+
.world
355+
.spawn()
356+
.insert_bundle((
357+
Transform::identity(),
358+
GlobalTransform::default(),
359+
Parent(child),
360+
))
361+
.id();
362+
app.world.spawn().insert_bundle((
363+
Transform::default(),
364+
GlobalTransform::default(),
365+
Children::with(&[child]),
366+
));
367+
app.world.entity_mut(child).insert(Parent(grandchild));
368+
369+
app.update();
370+
}
325371
}

0 commit comments

Comments
 (0)