Skip to content

Commit f32ee63

Browse files
nicopapvybjoseph-gio
authored
Fix transform propagation of orphaned entities (#7264)
# Objective - Fix #7263 This has nothing to do with #7024. This is for the case where the user opted to **not** keep the same global transform on update. ## Solution - Add a `RemovedComponent<Parent>` to `propagate_transforms` - Add a `RemovedComponent<Parent>` and `Local<Vec<Entity>>` to `sync_simple_transforms` - Add test to make sure all of this works. ### Performance note This should only incur a cost in cases where a parent is removed. A minimal overhead (one look up in the `removed_components` sparse set) per root entities without children which transform didn't change. A `Vec` the size of the largest number of entities removed with a `Parent` component in a single frame, and a binary search on a `Vec` per root entities. It could slow up considerably in situations where a lot of entities are orphaned consistently during every frame, since `sync_simple_transforms` is not parallel. But in this situation, it is likely that the overhead of archetype updates overwhelms everything. --- ## Changelog - Fix the `GlobalTransform` not getting updated when `Parent` is removed ## Migration Guide - If you called `bevy_transform::systems::sync_simple_transforms` and `bevy_transform::systems::propagate_transforms` (which is not re-exported by bevy) you need to account for the additional `RemovedComponents<Parent>` parameter. --------- Co-authored-by: vyb <[email protected]> Co-authored-by: JoJoJet <[email protected]>
1 parent 0d971a6 commit f32ee63

File tree

1 file changed

+81
-5
lines changed

1 file changed

+81
-5
lines changed

crates/bevy_transform/src/systems.rs

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,39 @@ use crate::components::{GlobalTransform, Transform};
22
use bevy_ecs::{
33
change_detection::Ref,
44
prelude::{Changed, DetectChanges, Entity, Query, With, Without},
5+
removal_detection::RemovedComponents,
6+
system::{Local, ParamSet},
57
};
68
use bevy_hierarchy::{Children, Parent};
79

810
/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy
911
///
1012
/// Third party plugins should ensure that this is used in concert with [`propagate_transforms`].
1113
pub fn sync_simple_transforms(
12-
mut query: Query<
13-
(&Transform, &mut GlobalTransform),
14-
(Changed<Transform>, Without<Parent>, Without<Children>),
15-
>,
14+
mut query: ParamSet<(
15+
Query<
16+
(&Transform, &mut GlobalTransform),
17+
(Changed<Transform>, Without<Parent>, Without<Children>),
18+
>,
19+
Query<(Ref<Transform>, &mut GlobalTransform), Without<Children>>,
20+
)>,
21+
mut orphaned: RemovedComponents<Parent>,
1622
) {
23+
// Update changed entities.
1724
query
25+
.p0()
1826
.par_iter_mut()
1927
.for_each_mut(|(transform, mut global_transform)| {
2028
*global_transform = GlobalTransform::from(*transform);
2129
});
30+
// Update orphaned entities.
31+
let mut query = query.p1();
32+
let mut iter = query.iter_many_mut(orphaned.iter());
33+
while let Some((transform, mut global_transform)) = iter.fetch_next() {
34+
if !transform.is_changed() {
35+
*global_transform = GlobalTransform::from(*transform);
36+
}
37+
}
2238
}
2339

2440
/// Update [`GlobalTransform`] component of entities based on entity hierarchy and
@@ -30,12 +46,17 @@ pub fn propagate_transforms(
3046
(Entity, &Children, Ref<Transform>, &mut GlobalTransform),
3147
Without<Parent>,
3248
>,
49+
mut orphaned: RemovedComponents<Parent>,
3350
transform_query: Query<(Ref<Transform>, &mut GlobalTransform, Option<&Children>), With<Parent>>,
3451
parent_query: Query<(Entity, Ref<Parent>)>,
52+
mut orphaned_entities: Local<Vec<Entity>>,
3553
) {
54+
orphaned_entities.clear();
55+
orphaned_entities.extend(orphaned.iter());
56+
orphaned_entities.sort_unstable();
3657
root_query.par_iter_mut().for_each_mut(
3758
|(entity, children, transform, mut global_transform)| {
38-
let changed = transform.is_changed();
59+
let changed = transform.is_changed() || orphaned_entities.binary_search(&entity).is_ok();
3960
if changed {
4061
*global_transform = GlobalTransform::from(*transform);
4162
}
@@ -165,6 +186,61 @@ mod test {
165186
use crate::TransformBundle;
166187
use bevy_hierarchy::{BuildChildren, BuildWorldChildren, Children, Parent};
167188

189+
#[test]
190+
fn correct_parent_removed() {
191+
ComputeTaskPool::init(TaskPool::default);
192+
let mut world = World::default();
193+
let offset_global_transform =
194+
|offset| GlobalTransform::from(Transform::from_xyz(offset, offset, offset));
195+
let offset_transform =
196+
|offset| TransformBundle::from_transform(Transform::from_xyz(offset, offset, offset));
197+
198+
let mut schedule = Schedule::new();
199+
schedule.add_systems((sync_simple_transforms, propagate_transforms));
200+
201+
let mut command_queue = CommandQueue::default();
202+
let mut commands = Commands::new(&mut command_queue, &world);
203+
let root = commands.spawn(offset_transform(3.3)).id();
204+
let parent = commands.spawn(offset_transform(4.4)).id();
205+
let child = commands.spawn(offset_transform(5.5)).id();
206+
commands.entity(parent).set_parent(root);
207+
commands.entity(child).set_parent(parent);
208+
command_queue.apply(&mut world);
209+
schedule.run(&mut world);
210+
211+
assert_eq!(
212+
world.get::<GlobalTransform>(parent).unwrap(),
213+
&offset_global_transform(4.4 + 3.3),
214+
"The transform systems didn't run, ie: `GlobalTransform` wasn't updated",
215+
);
216+
217+
// Remove parent of `parent`
218+
let mut command_queue = CommandQueue::default();
219+
let mut commands = Commands::new(&mut command_queue, &world);
220+
commands.entity(parent).remove_parent();
221+
command_queue.apply(&mut world);
222+
schedule.run(&mut world);
223+
224+
assert_eq!(
225+
world.get::<GlobalTransform>(parent).unwrap(),
226+
&offset_global_transform(4.4),
227+
"The global transform of an orphaned entity wasn't updated properly",
228+
);
229+
230+
// Remove parent of `child`
231+
let mut command_queue = CommandQueue::default();
232+
let mut commands = Commands::new(&mut command_queue, &world);
233+
commands.entity(child).remove_parent();
234+
command_queue.apply(&mut world);
235+
schedule.run(&mut world);
236+
237+
assert_eq!(
238+
world.get::<GlobalTransform>(child).unwrap(),
239+
&offset_global_transform(5.5),
240+
"The global transform of an orphaned entity wasn't updated properly",
241+
);
242+
}
243+
168244
#[test]
169245
fn did_propagate() {
170246
ComputeTaskPool::init(TaskPool::default);

0 commit comments

Comments
 (0)