bevy_transform/
systems.rs

1use crate::components::{GlobalTransform, Transform};
2use bevy_ecs::{
3    change_detection::Ref,
4    prelude::{Changed, DetectChanges, Entity, Query, With, Without},
5    query::{Added, Or},
6    removal_detection::RemovedComponents,
7    system::{Local, ParamSet},
8};
9use bevy_hierarchy::{Children, Parent};
10
11/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy
12///
13/// Third party plugins should ensure that this is used in concert with [`propagate_transforms`].
14pub fn sync_simple_transforms(
15    mut query: ParamSet<(
16        Query<
17            (&Transform, &mut GlobalTransform),
18            (
19                Or<(Changed<Transform>, Added<GlobalTransform>)>,
20                Without<Parent>,
21                Without<Children>,
22            ),
23        >,
24        Query<(Ref<Transform>, &mut GlobalTransform), (Without<Parent>, Without<Children>)>,
25    )>,
26    mut orphaned: RemovedComponents<Parent>,
27) {
28    // Update changed entities.
29    query
30        .p0()
31        .par_iter_mut()
32        .for_each(|(transform, mut global_transform)| {
33            *global_transform = GlobalTransform::from(*transform);
34        });
35    // Update orphaned entities.
36    let mut query = query.p1();
37    let mut iter = query.iter_many_mut(orphaned.read());
38    while let Some((transform, mut global_transform)) = iter.fetch_next() {
39        if !transform.is_changed() && !global_transform.is_added() {
40            *global_transform = GlobalTransform::from(*transform);
41        }
42    }
43}
44
45/// Update [`GlobalTransform`] component of entities based on entity hierarchy and
46/// [`Transform`] component.
47///
48/// Third party plugins should ensure that this is used in concert with [`sync_simple_transforms`].
49pub fn propagate_transforms(
50    mut root_query: Query<
51        (Entity, &Children, Ref<Transform>, &mut GlobalTransform),
52        Without<Parent>,
53    >,
54    mut orphaned: RemovedComponents<Parent>,
55    transform_query: Query<(Ref<Transform>, &mut GlobalTransform, Option<&Children>), With<Parent>>,
56    parent_query: Query<(Entity, Ref<Parent>), With<GlobalTransform>>,
57    mut orphaned_entities: Local<Vec<Entity>>,
58) {
59    orphaned_entities.clear();
60    orphaned_entities.extend(orphaned.read());
61    orphaned_entities.sort_unstable();
62    root_query.par_iter_mut().for_each(
63        |(entity, children, transform, mut global_transform)| {
64            let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
65            if changed {
66                *global_transform = GlobalTransform::from(*transform);
67            }
68
69            for (child, actual_parent) in parent_query.iter_many(children) {
70                assert_eq!(
71                    actual_parent.get(), entity,
72                    "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
73                );
74                // SAFETY:
75                // - `child` must have consistent parentage, or the above assertion would panic.
76                // Since `child` is parented to a root entity, the entire hierarchy leading to it is consistent.
77                // - We may operate as if all descendants are consistent, since `propagate_recursive` will panic before 
78                //   continuing to propagate if it encounters an entity with inconsistent parentage.
79                // - Since each root entity is unique and the hierarchy is consistent and forest-like,
80                //   other root entities' `propagate_recursive` calls will not conflict with this one.
81                // - Since this is the only place where `transform_query` gets used, there will be no conflicting fetches elsewhere.
82                #[expect(unsafe_code, reason = "`propagate_recursive()` is unsafe due to its use of `Query::get_unchecked()`.")]
83                unsafe {
84                    propagate_recursive(
85                        &global_transform,
86                        &transform_query,
87                        &parent_query,
88                        child,
89                        changed || actual_parent.is_changed(),
90                    );
91                }
92            }
93        },
94    );
95}
96
97/// Recursively propagates the transforms for `entity` and all of its descendants.
98///
99/// # Panics
100///
101/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before propagating
102/// the transforms of any malformed entities and their descendants.
103///
104/// # Safety
105///
106/// - While this function is running, `transform_query` must not have any fetches for `entity`,
107///     nor any of its descendants.
108/// - The caller must ensure that the hierarchy leading to `entity`
109///     is well-formed and must remain as a tree or a forest. Each entity must have at most one parent.
110#[expect(
111    unsafe_code,
112    reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met."
113)]
114unsafe fn propagate_recursive(
115    parent: &GlobalTransform,
116    transform_query: &Query<
117        (Ref<Transform>, &mut GlobalTransform, Option<&Children>),
118        With<Parent>,
119    >,
120    parent_query: &Query<(Entity, Ref<Parent>), With<GlobalTransform>>,
121    entity: Entity,
122    mut changed: bool,
123) {
124    let (global_matrix, children) = {
125        let Ok((transform, mut global_transform, children)) =
126            // SAFETY: This call cannot create aliased mutable references.
127            //   - The top level iteration parallelizes on the roots of the hierarchy.
128            //   - The caller ensures that each child has one and only one unique parent throughout the entire
129            //     hierarchy.
130            //
131            // For example, consider the following malformed hierarchy:
132            //
133            //     A
134            //   /   \
135            //  B     C
136            //   \   /
137            //     D
138            //
139            // D has two parents, B and C. If the propagation passes through C, but the Parent component on D points to B,
140            // the above check will panic as the origin parent does match the recorded parent.
141            //
142            // Also consider the following case, where A and B are roots:
143            //
144            //  A       B
145            //   \     /
146            //    C   D
147            //     \ /
148            //      E
149            //
150            // Even if these A and B start two separate tasks running in parallel, one of them will panic before attempting
151            // to mutably access E.
152            (unsafe { transform_query.get_unchecked(entity) }) else {
153                return;
154            };
155
156        changed |= transform.is_changed() || global_transform.is_added();
157        if changed {
158            *global_transform = parent.mul_transform(*transform);
159        }
160        (global_transform, children)
161    };
162
163    let Some(children) = children else { return };
164    for (child, actual_parent) in parent_query.iter_many(children) {
165        assert_eq!(
166            actual_parent.get(), entity,
167            "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
168        );
169        // SAFETY: The caller guarantees that `transform_query` will not be fetched
170        // for any descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
171        //
172        // The above assertion ensures that each child has one and only one unique parent throughout the
173        // entire hierarchy.
174        unsafe {
175            propagate_recursive(
176                global_matrix.as_ref(),
177                transform_query,
178                parent_query,
179                child,
180                changed || actual_parent.is_changed(),
181            );
182        }
183    }
184}
185
186#[cfg(test)]
187mod test {
188    use bevy_app::prelude::*;
189    use bevy_ecs::{prelude::*, world::CommandQueue};
190    use bevy_math::{vec3, Vec3};
191    use bevy_tasks::{ComputeTaskPool, TaskPool};
192
193    use crate::systems::*;
194    use bevy_hierarchy::{BuildChildren, ChildBuild};
195
196    #[test]
197    fn correct_parent_removed() {
198        ComputeTaskPool::get_or_init(TaskPool::default);
199        let mut world = World::default();
200        let offset_global_transform =
201            |offset| GlobalTransform::from(Transform::from_xyz(offset, offset, offset));
202        let offset_transform = |offset| Transform::from_xyz(offset, offset, offset);
203
204        let mut schedule = Schedule::default();
205        schedule.add_systems((sync_simple_transforms, propagate_transforms));
206
207        let mut command_queue = CommandQueue::default();
208        let mut commands = Commands::new(&mut command_queue, &world);
209        let root = commands.spawn(offset_transform(3.3)).id();
210        let parent = commands.spawn(offset_transform(4.4)).id();
211        let child = commands.spawn(offset_transform(5.5)).id();
212        commands.entity(parent).set_parent(root);
213        commands.entity(child).set_parent(parent);
214        command_queue.apply(&mut world);
215        schedule.run(&mut world);
216
217        assert_eq!(
218            world.get::<GlobalTransform>(parent).unwrap(),
219            &offset_global_transform(4.4 + 3.3),
220            "The transform systems didn't run, ie: `GlobalTransform` wasn't updated",
221        );
222
223        // Remove parent of `parent`
224        let mut command_queue = CommandQueue::default();
225        let mut commands = Commands::new(&mut command_queue, &world);
226        commands.entity(parent).remove_parent();
227        command_queue.apply(&mut world);
228        schedule.run(&mut world);
229
230        assert_eq!(
231            world.get::<GlobalTransform>(parent).unwrap(),
232            &offset_global_transform(4.4),
233            "The global transform of an orphaned entity wasn't updated properly",
234        );
235
236        // Remove parent of `child`
237        let mut command_queue = CommandQueue::default();
238        let mut commands = Commands::new(&mut command_queue, &world);
239        commands.entity(child).remove_parent();
240        command_queue.apply(&mut world);
241        schedule.run(&mut world);
242
243        assert_eq!(
244            world.get::<GlobalTransform>(child).unwrap(),
245            &offset_global_transform(5.5),
246            "The global transform of an orphaned entity wasn't updated properly",
247        );
248    }
249
250    #[test]
251    fn did_propagate() {
252        ComputeTaskPool::get_or_init(TaskPool::default);
253        let mut world = World::default();
254
255        let mut schedule = Schedule::default();
256        schedule.add_systems((sync_simple_transforms, propagate_transforms));
257
258        // Root entity
259        world.spawn(Transform::from_xyz(1.0, 0.0, 0.0));
260
261        let mut children = Vec::new();
262        world
263            .spawn(Transform::from_xyz(1.0, 0.0, 0.0))
264            .with_children(|parent| {
265                children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.)).id());
266                children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.)).id());
267            });
268        schedule.run(&mut world);
269
270        assert_eq!(
271            *world.get::<GlobalTransform>(children[0]).unwrap(),
272            GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
273        );
274
275        assert_eq!(
276            *world.get::<GlobalTransform>(children[1]).unwrap(),
277            GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
278        );
279    }
280
281    #[test]
282    fn did_propagate_command_buffer() {
283        let mut world = World::default();
284
285        let mut schedule = Schedule::default();
286        schedule.add_systems((sync_simple_transforms, propagate_transforms));
287
288        // Root entity
289        let mut queue = CommandQueue::default();
290        let mut commands = Commands::new(&mut queue, &world);
291        let mut children = Vec::new();
292        commands
293            .spawn(Transform::from_xyz(1.0, 0.0, 0.0))
294            .with_children(|parent| {
295                children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
296                children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.0)).id());
297            });
298        queue.apply(&mut world);
299        schedule.run(&mut world);
300
301        assert_eq!(
302            *world.get::<GlobalTransform>(children[0]).unwrap(),
303            GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
304        );
305
306        assert_eq!(
307            *world.get::<GlobalTransform>(children[1]).unwrap(),
308            GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
309        );
310    }
311
312    #[test]
313    fn correct_children() {
314        ComputeTaskPool::get_or_init(TaskPool::default);
315        let mut world = World::default();
316
317        let mut schedule = Schedule::default();
318        schedule.add_systems((sync_simple_transforms, propagate_transforms));
319
320        // Add parent entities
321        let mut children = Vec::new();
322        let parent = {
323            let mut command_queue = CommandQueue::default();
324            let mut commands = Commands::new(&mut command_queue, &world);
325            let parent = commands.spawn(Transform::from_xyz(1.0, 0.0, 0.0)).id();
326            commands.entity(parent).with_children(|parent| {
327                children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
328                children.push(parent.spawn(Transform::from_xyz(0.0, 3.0, 0.0)).id());
329            });
330            command_queue.apply(&mut world);
331            schedule.run(&mut world);
332            parent
333        };
334
335        assert_eq!(
336            world
337                .get::<Children>(parent)
338                .unwrap()
339                .iter()
340                .cloned()
341                .collect::<Vec<_>>(),
342            children,
343        );
344
345        // Parent `e1` to `e2`.
346        {
347            let mut command_queue = CommandQueue::default();
348            let mut commands = Commands::new(&mut command_queue, &world);
349            commands.entity(children[1]).add_child(children[0]);
350            command_queue.apply(&mut world);
351            schedule.run(&mut world);
352        }
353
354        assert_eq!(
355            world
356                .get::<Children>(parent)
357                .unwrap()
358                .iter()
359                .cloned()
360                .collect::<Vec<_>>(),
361            vec![children[1]]
362        );
363
364        assert_eq!(
365            world
366                .get::<Children>(children[1])
367                .unwrap()
368                .iter()
369                .cloned()
370                .collect::<Vec<_>>(),
371            vec![children[0]]
372        );
373
374        assert!(world.despawn(children[0]));
375
376        schedule.run(&mut world);
377
378        assert_eq!(
379            world
380                .get::<Children>(parent)
381                .unwrap()
382                .iter()
383                .cloned()
384                .collect::<Vec<_>>(),
385            vec![children[1]]
386        );
387    }
388
389    #[test]
390    fn correct_transforms_when_no_children() {
391        let mut app = App::new();
392        ComputeTaskPool::get_or_init(TaskPool::default);
393
394        app.add_systems(Update, (sync_simple_transforms, propagate_transforms));
395
396        let translation = vec3(1.0, 0.0, 0.0);
397
398        // These will be overwritten.
399        let mut child = Entity::from_raw(0);
400        let mut grandchild = Entity::from_raw(1);
401        let parent = app
402            .world_mut()
403            .spawn(Transform::from_translation(translation))
404            .with_children(|builder| {
405                child = builder
406                    .spawn(Transform::IDENTITY)
407                    .with_children(|builder| {
408                        grandchild = builder.spawn(Transform::IDENTITY).id();
409                    })
410                    .id();
411            })
412            .id();
413
414        app.update();
415
416        // check the `Children` structure is spawned
417        assert_eq!(&**app.world().get::<Children>(parent).unwrap(), &[child]);
418        assert_eq!(
419            &**app.world().get::<Children>(child).unwrap(),
420            &[grandchild]
421        );
422        // Note that at this point, the `GlobalTransform`s will not have updated yet, due to `Commands` delay
423        app.update();
424
425        let mut state = app.world_mut().query::<&GlobalTransform>();
426        for global in state.iter(app.world()) {
427            assert_eq!(global, &GlobalTransform::from_translation(translation));
428        }
429    }
430
431    #[test]
432    #[should_panic]
433    fn panic_when_hierarchy_cycle() {
434        ComputeTaskPool::get_or_init(TaskPool::default);
435        // We cannot directly edit Parent and Children, so we use a temp world to break
436        // the hierarchy's invariants.
437        let mut temp = World::new();
438        let mut app = App::new();
439
440        app.add_systems(Update, (propagate_transforms, sync_simple_transforms));
441
442        fn setup_world(world: &mut World) -> (Entity, Entity) {
443            let mut grandchild = Entity::from_raw(0);
444            let child = world
445                .spawn(Transform::IDENTITY)
446                .with_children(|builder| {
447                    grandchild = builder.spawn(Transform::IDENTITY).id();
448                })
449                .id();
450            (child, grandchild)
451        }
452
453        let (temp_child, temp_grandchild) = setup_world(&mut temp);
454        let (child, grandchild) = setup_world(app.world_mut());
455
456        assert_eq!(temp_child, child);
457        assert_eq!(temp_grandchild, grandchild);
458
459        app.world_mut()
460            .spawn(Transform::IDENTITY)
461            .add_children(&[child]);
462        core::mem::swap(
463            &mut *app.world_mut().get_mut::<Parent>(child).unwrap(),
464            &mut *temp.get_mut::<Parent>(grandchild).unwrap(),
465        );
466
467        app.update();
468    }
469
470    #[test]
471    fn global_transform_should_not_be_overwritten_after_reparenting() {
472        let translation = Vec3::ONE;
473        let mut world = World::new();
474
475        // Create transform propagation schedule
476        let mut schedule = Schedule::default();
477        schedule.add_systems((sync_simple_transforms, propagate_transforms));
478
479        // Spawn a `Transform` entity with a local translation of `Vec3::ONE`
480        let mut spawn_transform_bundle =
481            || world.spawn(Transform::from_translation(translation)).id();
482
483        // Spawn parent and child with identical transform bundles
484        let parent = spawn_transform_bundle();
485        let child = spawn_transform_bundle();
486        world.entity_mut(parent).add_child(child);
487
488        // Run schedule to propagate transforms
489        schedule.run(&mut world);
490
491        // Child should be positioned relative to its parent
492        let parent_global_transform = *world.entity(parent).get::<GlobalTransform>().unwrap();
493        let child_global_transform = *world.entity(child).get::<GlobalTransform>().unwrap();
494        assert!(parent_global_transform
495            .translation()
496            .abs_diff_eq(translation, 0.1));
497        assert!(child_global_transform
498            .translation()
499            .abs_diff_eq(2. * translation, 0.1));
500
501        // Reparent child
502        world.entity_mut(child).remove_parent();
503        world.entity_mut(parent).add_child(child);
504
505        // Run schedule to propagate transforms
506        schedule.run(&mut world);
507
508        // Translations should be unchanged after update
509        assert_eq!(
510            parent_global_transform,
511            *world.entity(parent).get::<GlobalTransform>().unwrap()
512        );
513        assert_eq!(
514            child_global_transform,
515            *world.entity(child).get::<GlobalTransform>().unwrap()
516        );
517    }
518}