bevy_transform/
systems.rs

1use crate::components::{GlobalTransform, Transform, TransformTreeChanged};
2use bevy_ecs::prelude::*;
3#[cfg(feature = "std")]
4pub use parallel::propagate_parent_transforms;
5#[cfg(not(feature = "std"))]
6pub use serial::propagate_parent_transforms;
7
8/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy
9///
10/// Third party plugins should ensure that this is used in concert with
11/// [`propagate_parent_transforms`] and [`mark_dirty_trees`].
12pub fn sync_simple_transforms(
13    mut query: ParamSet<(
14        Query<
15            (&Transform, &mut GlobalTransform),
16            (
17                Or<(Changed<Transform>, Added<GlobalTransform>)>,
18                Without<ChildOf>,
19                Without<Children>,
20            ),
21        >,
22        Query<(Ref<Transform>, &mut GlobalTransform), (Without<ChildOf>, Without<Children>)>,
23    )>,
24    mut orphaned: RemovedComponents<ChildOf>,
25) {
26    // Update changed entities.
27    query
28        .p0()
29        .par_iter_mut()
30        .for_each(|(transform, mut global_transform)| {
31            *global_transform = GlobalTransform::from(*transform);
32        });
33    // Update orphaned entities.
34    let mut query = query.p1();
35    let mut iter = query.iter_many_mut(orphaned.read());
36    while let Some((transform, mut global_transform)) = iter.fetch_next() {
37        if !transform.is_changed() && !global_transform.is_added() {
38            *global_transform = GlobalTransform::from(*transform);
39        }
40    }
41}
42
43/// Optimization for static scenes. Propagates a "dirty bit" up the hierarchy towards ancestors.
44/// Transform propagation can ignore entire subtrees of the hierarchy if it encounters an entity
45/// without the dirty bit.
46pub fn mark_dirty_trees(
47    changed_transforms: Query<
48        Entity,
49        Or<(Changed<Transform>, Changed<ChildOf>, Added<GlobalTransform>)>,
50    >,
51    mut orphaned: RemovedComponents<ChildOf>,
52    mut transforms: Query<(Option<&ChildOf>, &mut TransformTreeChanged)>,
53) {
54    for entity in changed_transforms.iter().chain(orphaned.read()) {
55        let mut next = entity;
56        while let Ok((child_of, mut tree)) = transforms.get_mut(next) {
57            if tree.is_changed() && !tree.is_added() {
58                // If the component was changed, this part of the tree has already been processed.
59                // Ignore this if the change was caused by the component being added.
60                break;
61            }
62            tree.set_changed();
63            if let Some(parent) = child_of.map(ChildOf::parent) {
64                next = parent;
65            } else {
66                break;
67            };
68        }
69    }
70}
71
72// TODO: This serial implementation isn't actually serial, it parallelizes across the roots.
73// Additionally, this couples "no_std" with "single_threaded" when these two features should be
74// independent.
75//
76// What we want to do in a future refactor is take the current "single threaded" implementation, and
77// actually make it single threaded. This will remove any overhead associated with working on a task
78// pool when you only have a single thread, and will have the benefit of removing the need for any
79// unsafe. We would then make the multithreaded implementation work across std and no_std, but this
80// is blocked a no_std compatible Channel, which is why this TODO is not yet implemented.
81//
82// This complexity might also not be needed. If the multithreaded implementation on a single thread
83// is as fast as the single threaded implementation, we could simply remove the entire serial
84// module, and make the multithreaded module no_std compatible.
85//
86/// Serial hierarchy traversal. Useful in `no_std` or single threaded contexts.
87#[cfg(not(feature = "std"))]
88mod serial {
89    use crate::prelude::*;
90    use alloc::vec::Vec;
91    use bevy_ecs::prelude::*;
92
93    /// Update [`GlobalTransform`] component of entities based on entity hierarchy and [`Transform`]
94    /// component.
95    ///
96    /// Third party plugins should ensure that this is used in concert with
97    /// [`sync_simple_transforms`](super::sync_simple_transforms) and
98    /// [`mark_dirty_trees`](super::mark_dirty_trees).
99    pub fn propagate_parent_transforms(
100        mut root_query: Query<
101            (Entity, &Children, Ref<Transform>, &mut GlobalTransform),
102            Without<ChildOf>,
103        >,
104        mut orphaned: RemovedComponents<ChildOf>,
105        transform_query: Query<
106            (Ref<Transform>, &mut GlobalTransform, Option<&Children>),
107            With<ChildOf>,
108        >,
109        child_query: Query<(Entity, Ref<ChildOf>), With<GlobalTransform>>,
110        mut orphaned_entities: Local<Vec<Entity>>,
111    ) {
112        orphaned_entities.clear();
113        orphaned_entities.extend(orphaned.read());
114        orphaned_entities.sort_unstable();
115        root_query.par_iter_mut().for_each(
116        |(entity, children, transform, mut global_transform)| {
117            let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
118            if changed {
119                *global_transform = GlobalTransform::from(*transform);
120            }
121
122            for (child, child_of) in child_query.iter_many(children) {
123                assert_eq!(
124                    child_of.parent(), entity,
125                    "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
126                );
127                // SAFETY:
128                // - `child` must have consistent parentage, or the above assertion would panic.
129                //   Since `child` is parented to a root entity, the entire hierarchy leading to it
130                //   is consistent.
131                // - We may operate as if all descendants are consistent, since
132                //   `propagate_recursive` will panic before continuing to propagate if it
133                //   encounters an entity with inconsistent parentage.
134                // - Since each root entity is unique and the hierarchy is consistent and
135                //   forest-like, other root entities' `propagate_recursive` calls will not conflict
136                //   with this one.
137                // - Since this is the only place where `transform_query` gets used, there will be
138                //   no conflicting fetches elsewhere.
139                #[expect(unsafe_code, reason = "`propagate_recursive()` is unsafe due to its use of `Query::get_unchecked()`.")]
140                unsafe {
141                    propagate_recursive(
142                        &global_transform,
143                        &transform_query,
144                        &child_query,
145                        child,
146                        changed || child_of.is_changed(),
147                    );
148                }
149            }
150        },
151    );
152    }
153
154    /// Recursively propagates the transforms for `entity` and all of its descendants.
155    ///
156    /// # Panics
157    ///
158    /// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before
159    /// propagating the transforms of any malformed entities and their descendants.
160    ///
161    /// # Safety
162    ///
163    /// - While this function is running, `transform_query` must not have any fetches for `entity`,
164    ///   nor any of its descendants.
165    /// - The caller must ensure that the hierarchy leading to `entity` is well-formed and must
166    ///   remain as a tree or a forest. Each entity must have at most one parent.
167    #[expect(
168        unsafe_code,
169        reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met."
170    )]
171    unsafe fn propagate_recursive(
172        parent: &GlobalTransform,
173        transform_query: &Query<
174            (Ref<Transform>, &mut GlobalTransform, Option<&Children>),
175            With<ChildOf>,
176        >,
177        child_query: &Query<(Entity, Ref<ChildOf>), With<GlobalTransform>>,
178        entity: Entity,
179        mut changed: bool,
180    ) {
181        let (global_matrix, children) = {
182            let Ok((transform, mut global_transform, children)) =
183            // SAFETY: This call cannot create aliased mutable references.
184            //   - The top level iteration parallelizes on the roots of the hierarchy.
185            //   - The caller ensures that each child has one and only one unique parent throughout
186            //     the entire hierarchy.
187            //
188            // For example, consider the following malformed hierarchy:
189            //
190            //     A
191            //   /   \
192            //  B     C
193            //   \   /
194            //     D
195            //
196            // D has two parents, B and C. If the propagation passes through C, but the ChildOf
197            // component on D points to B, the above check will panic as the origin parent does
198            // match the recorded parent.
199            //
200            // Also consider the following case, where A and B are roots:
201            //
202            //  A       B
203            //   \     /
204            //    C   D
205            //     \ /
206            //      E
207            //
208            // Even if these A and B start two separate tasks running in parallel, one of them will
209            // panic before attempting to mutably access E.
210            (unsafe { transform_query.get_unchecked(entity) }) else {
211                return;
212            };
213
214            changed |= transform.is_changed() || global_transform.is_added();
215            if changed {
216                *global_transform = parent.mul_transform(*transform);
217            }
218            (global_transform, children)
219        };
220
221        let Some(children) = children else { return };
222        for (child, child_of) in child_query.iter_many(children) {
223            assert_eq!(
224            child_of.parent(), entity,
225            "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
226        );
227            // SAFETY: The caller guarantees that `transform_query` will not be fetched for any
228            // descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
229            //
230            // The above assertion ensures that each child has one and only one unique parent
231            // throughout the entire hierarchy.
232            unsafe {
233                propagate_recursive(
234                    global_matrix.as_ref(),
235                    transform_query,
236                    child_query,
237                    child,
238                    changed || child_of.is_changed(),
239                );
240            }
241        }
242    }
243}
244
245// TODO: Relies on `std` until a `no_std` `mpsc` channel is available.
246//
247/// Parallel hierarchy traversal with a batched work sharing scheduler. Often 2-5 times faster than
248/// the serial version.
249#[cfg(feature = "std")]
250mod parallel {
251    use crate::prelude::*;
252    // TODO: this implementation could be used in no_std if there are equivalents of these.
253    use alloc::{sync::Arc, vec::Vec};
254    use bevy_ecs::{entity::UniqueEntityIter, prelude::*, system::lifetimeless::Read};
255    use bevy_tasks::{ComputeTaskPool, TaskPool};
256    use bevy_utils::Parallel;
257    use core::sync::atomic::{AtomicI32, Ordering};
258    use std::sync::{
259        mpsc::{Receiver, Sender},
260        Mutex,
261    };
262
263    /// Update [`GlobalTransform`] component of entities based on entity hierarchy and [`Transform`]
264    /// component.
265    ///
266    /// Third party plugins should ensure that this is used in concert with
267    /// [`sync_simple_transforms`](super::sync_simple_transforms) and
268    /// [`mark_dirty_trees`](super::mark_dirty_trees).
269    pub fn propagate_parent_transforms(
270        mut queue: Local<WorkQueue>,
271        mut roots: Query<
272            (Entity, Ref<Transform>, &mut GlobalTransform, &Children),
273            (Without<ChildOf>, Changed<TransformTreeChanged>),
274        >,
275        nodes: NodeQuery,
276    ) {
277        // Process roots in parallel, seeding the work queue
278        roots.par_iter_mut().for_each_init(
279            || queue.local_queue.borrow_local_mut(),
280            |outbox, (parent, transform, mut parent_transform, children)| {
281                *parent_transform = GlobalTransform::from(*transform);
282
283                // SAFETY: the parent entities passed into this function are taken from iterating
284                // over the root entity query. Queries iterate over disjoint entities, preventing
285                // mutable aliasing, and making this call safe.
286                #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
287                unsafe {
288                    propagate_descendants_unchecked(
289                        parent,
290                        parent_transform,
291                        children,
292                        &nodes,
293                        outbox,
294                        &queue,
295                        // Need to revisit this single-max-depth by profiling more representative
296                        // scenes. It's possible that it is actually beneficial to go deep into the
297                        // hierarchy to build up a good task queue before starting the workers.
298                        // However, we avoid this for now to prevent cases where only a single
299                        // thread is going deep into the hierarchy while the others sit idle, which
300                        // is the problem that the tasks sharing workers already solve.
301                        1,
302                    );
303                }
304            },
305        );
306        // Send all tasks in thread local outboxes *after* roots are processed to reduce the total
307        // number of channel sends by avoiding sending partial batches.
308        queue.send_batches();
309
310        if let Ok(rx) = queue.receiver.try_lock() {
311            if let Some(task) = rx.try_iter().next() {
312                // This is a bit silly, but the only way to see if there is any work is to grab a
313                // task. Peeking will remove the task even if you don't call `next`, resulting in
314                // dropping a task. What we do here is grab the first task if there is one, then
315                // immediately send it to the back of the queue.
316                queue.sender.send(task).ok();
317            } else {
318                return; // No work, don't bother spawning any tasks
319            }
320        }
321
322        // Spawn workers on the task pool to recursively propagate the hierarchy in parallel.
323        let task_pool = ComputeTaskPool::get_or_init(TaskPool::default);
324        task_pool.scope(|s| {
325            (1..task_pool.thread_num()) // First worker is run locally instead of the task pool.
326                .for_each(|_| s.spawn(async { propagation_worker(&queue, &nodes) }));
327            propagation_worker(&queue, &nodes);
328        });
329    }
330
331    /// A parallel worker that will consume processed parent entities from the queue, and push
332    /// children to the queue once it has propagated their [`GlobalTransform`].
333    #[inline]
334    fn propagation_worker(queue: &WorkQueue, nodes: &NodeQuery) {
335        #[cfg(feature = "std")]
336        let _span = bevy_log::info_span!("transform propagation worker").entered();
337
338        let mut outbox = queue.local_queue.borrow_local_mut();
339        loop {
340            // Try to acquire a lock on the work queue in a tight loop. Profiling shows this is much
341            // more efficient than relying on `.lock()`, which causes gaps to form between tasks.
342            let Ok(rx) = queue.receiver.try_lock() else {
343                core::hint::spin_loop(); // No apparent impact on profiles, but best practice.
344                continue;
345            };
346            // If the queue is empty and no other threads are busy processing work, we can conclude
347            // there is no more work to do, and end the task by exiting the loop.
348            let Some(mut tasks) = rx.try_iter().next() else {
349                if queue.busy_threads.load(Ordering::Relaxed) == 0 {
350                    break; // All work is complete, kill the worker
351                }
352                continue; // No work to do now, but another thread is busy creating more work.
353            };
354            if tasks.is_empty() {
355                continue; // This shouldn't happen, but if it does, we might as well stop early.
356            }
357
358            // If the task queue is extremely short, it's worthwhile to gather a few more tasks to
359            // reduce the amount of thread synchronization needed once this very short task is
360            // complete.
361            while tasks.len() < WorkQueue::CHUNK_SIZE / 2 {
362                let Some(mut extra_task) = rx.try_iter().next() else {
363                    break;
364                };
365                tasks.append(&mut extra_task);
366            }
367
368            // At this point, we know there is work to do, so we increment the busy thread counter,
369            // and drop the mutex guard *after* we have incremented the counter. This ensures that
370            // if another thread is able to acquire a lock, the busy thread counter will already be
371            // incremented.
372            queue.busy_threads.fetch_add(1, Ordering::Relaxed);
373            drop(rx); // Important: drop after atomic and before work starts.
374
375            for parent in tasks.drain(..) {
376                // SAFETY: each task pushed to the worker queue represents an unprocessed subtree of
377                // the hierarchy, guaranteeing unique access.
378                #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
379                unsafe {
380                    let (_, (_, p_global_transform, _), (p_children, _)) =
381                        nodes.get_unchecked(parent).unwrap();
382                    propagate_descendants_unchecked(
383                        parent,
384                        p_global_transform,
385                        p_children.unwrap(), // All entities in the queue should have children
386                        nodes,
387                        &mut outbox,
388                        queue,
389                        // Only affects performance. Trees deeper than this will still be fully
390                        // propagated, but the work will be broken into multiple tasks. This number
391                        // was chosen to be larger than any reasonable tree depth, while not being
392                        // so large the function could hang on a deep hierarchy.
393                        10_000,
394                    );
395                }
396            }
397            WorkQueue::send_batches_with(&queue.sender, &mut outbox);
398            queue.busy_threads.fetch_add(-1, Ordering::Relaxed);
399        }
400    }
401
402    /// Propagate transforms from `parent` to its `children`, pushing updated child entities to the
403    /// `outbox`. This function will continue propagating transforms to descendants in a depth-first
404    /// traversal, while simultaneously pushing unvisited branches to the outbox, for other threads
405    /// to take when idle.
406    ///
407    /// # Safety
408    ///
409    /// Callers must ensure that concurrent calls to this function are given unique `parent`
410    /// entities. Calling this function concurrently with the same `parent` is unsound. This
411    /// function will validate that the entity hierarchy does not contain cycles to prevent mutable
412    /// aliasing during propagation, but it is unable to verify that it isn't being used to mutably
413    /// alias the same entity.
414    ///
415    /// ## Panics
416    ///
417    /// Panics if the parent of a child node is not the same as the supplied `parent`. This
418    /// assertion ensures that the hierarchy is acyclic, which in turn ensures that if the caller is
419    /// following the supplied safety rules, multi-threaded propagation is sound.
420    #[inline]
421    #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
422    unsafe fn propagate_descendants_unchecked(
423        parent: Entity,
424        p_global_transform: Mut<GlobalTransform>,
425        p_children: &Children,
426        nodes: &NodeQuery,
427        outbox: &mut Vec<Entity>,
428        queue: &WorkQueue,
429        max_depth: usize,
430    ) {
431        // Create mutable copies of the input variables, used for iterative depth-first traversal.
432        let (mut parent, mut p_global_transform, mut p_children) =
433            (parent, p_global_transform, p_children);
434
435        // See the optimization note at the end to understand why this loop is here.
436        for depth in 1..=max_depth {
437            // Safety: traversing the entity tree from the roots, we assert that the childof and
438            // children pointers match in both directions (see assert below) to ensure the hierarchy
439            // does not have any cycles. Because the hierarchy does not have cycles, we know we are
440            // visiting disjoint entities in parallel, which is safe.
441            #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
442            let children_iter = unsafe {
443                nodes.iter_many_unique_unsafe(UniqueEntityIter::from_iterator_unchecked(
444                    p_children.iter(),
445                ))
446            };
447
448            let mut last_child = None;
449            let new_children = children_iter.filter_map(
450                |(child, (transform, mut global_transform, tree), (children, child_of))| {
451                    if !tree.is_changed() && !p_global_transform.is_changed() {
452                        // Static scene optimization
453                        return None;
454                    }
455                    assert_eq!(child_of.parent(), parent);
456
457                    // Transform prop is expensive - this helps avoid updating entire subtrees if
458                    // the GlobalTransform is unchanged, at the cost of an added equality check.
459                    global_transform.set_if_neq(p_global_transform.mul_transform(*transform));
460
461                    children.map(|children| {
462                        // Only continue propagation if the entity has children.
463                        last_child = Some((child, global_transform, children));
464                        child
465                    })
466                },
467            );
468            outbox.extend(new_children);
469
470            if depth >= max_depth || last_child.is_none() {
471                break; // Don't remove anything from the outbox or send any chunks, just exit.
472            }
473
474            // Optimization: tasks should consume work locally as long as they can to avoid
475            // thread synchronization for as long as possible.
476            if let Some(last_child) = last_child {
477                // Overwrite parent data with children, and loop to iterate through descendants.
478                (parent, p_global_transform, p_children) = last_child;
479                outbox.pop();
480
481                // Send chunks during traversal. This allows sharing tasks with other threads before
482                // fully completing the traversal.
483                if outbox.len() >= WorkQueue::CHUNK_SIZE {
484                    WorkQueue::send_batches_with(&queue.sender, outbox);
485                }
486            }
487        }
488    }
489
490    /// Alias for a large, repeatedly used query. Queries for transform entities that have both a
491    /// parent and possibly children, thus they are not roots.
492    type NodeQuery<'w, 's> = Query<
493        'w,
494        's,
495        (
496            Entity,
497            (
498                Ref<'static, Transform>,
499                Mut<'static, GlobalTransform>,
500                Ref<'static, TransformTreeChanged>,
501            ),
502            (Option<Read<Children>>, Read<ChildOf>),
503        ),
504    >;
505
506    /// A queue shared between threads for transform propagation.
507    pub struct WorkQueue {
508        /// A semaphore that tracks how many threads are busy doing work. Used to determine when
509        /// there is no more work to do.
510        busy_threads: AtomicI32,
511        sender: Sender<Vec<Entity>>,
512        receiver: Arc<Mutex<Receiver<Vec<Entity>>>>,
513        local_queue: Parallel<Vec<Entity>>,
514    }
515    impl Default for WorkQueue {
516        fn default() -> Self {
517            let (tx, rx) = std::sync::mpsc::channel();
518            Self {
519                busy_threads: AtomicI32::default(),
520                sender: tx,
521                receiver: Arc::new(Mutex::new(rx)),
522                local_queue: Default::default(),
523            }
524        }
525    }
526    impl WorkQueue {
527        const CHUNK_SIZE: usize = 512;
528
529        #[inline]
530        fn send_batches_with(sender: &Sender<Vec<Entity>>, outbox: &mut Vec<Entity>) {
531            for chunk in outbox
532                .chunks(WorkQueue::CHUNK_SIZE)
533                .filter(|c| !c.is_empty())
534            {
535                sender.send(chunk.to_vec()).ok();
536            }
537            outbox.clear();
538        }
539
540        #[inline]
541        fn send_batches(&mut self) {
542            let Self {
543                sender,
544                local_queue,
545                ..
546            } = self;
547            // Iterate over the locals to send batched tasks, avoiding the need to drain the locals
548            // into a larger allocation.
549            local_queue
550                .iter_mut()
551                .for_each(|outbox| Self::send_batches_with(sender, outbox));
552        }
553    }
554}
555
556#[cfg(test)]
557mod test {
558    use alloc::{vec, vec::Vec};
559    use bevy_app::prelude::*;
560    use bevy_ecs::{prelude::*, world::CommandQueue};
561    use bevy_math::{vec3, Vec3};
562    use bevy_tasks::{ComputeTaskPool, TaskPool};
563
564    use crate::systems::*;
565
566    #[test]
567    fn correct_parent_removed() {
568        ComputeTaskPool::get_or_init(TaskPool::default);
569        let mut world = World::default();
570        let offset_global_transform =
571            |offset| GlobalTransform::from(Transform::from_xyz(offset, offset, offset));
572        let offset_transform = |offset| Transform::from_xyz(offset, offset, offset);
573
574        let mut schedule = Schedule::default();
575        schedule.add_systems(
576            (
577                mark_dirty_trees,
578                sync_simple_transforms,
579                propagate_parent_transforms,
580            )
581                .chain(),
582        );
583
584        let mut command_queue = CommandQueue::default();
585        let mut commands = Commands::new(&mut command_queue, &world);
586        let root = commands.spawn(offset_transform(3.3)).id();
587        let parent = commands.spawn(offset_transform(4.4)).id();
588        let child = commands.spawn(offset_transform(5.5)).id();
589        commands.entity(parent).insert(ChildOf(root));
590        commands.entity(child).insert(ChildOf(parent));
591        command_queue.apply(&mut world);
592        schedule.run(&mut world);
593
594        assert_eq!(
595            world.get::<GlobalTransform>(parent).unwrap(),
596            &offset_global_transform(4.4 + 3.3),
597            "The transform systems didn't run, ie: `GlobalTransform` wasn't updated",
598        );
599
600        // Remove parent of `parent`
601        let mut command_queue = CommandQueue::default();
602        let mut commands = Commands::new(&mut command_queue, &world);
603        commands.entity(parent).remove::<ChildOf>();
604        command_queue.apply(&mut world);
605        schedule.run(&mut world);
606
607        assert_eq!(
608            world.get::<GlobalTransform>(parent).unwrap(),
609            &offset_global_transform(4.4),
610            "The global transform of an orphaned entity wasn't updated properly",
611        );
612
613        // Remove parent of `child`
614        let mut command_queue = CommandQueue::default();
615        let mut commands = Commands::new(&mut command_queue, &world);
616        commands.entity(child).remove::<ChildOf>();
617        command_queue.apply(&mut world);
618        schedule.run(&mut world);
619
620        assert_eq!(
621            world.get::<GlobalTransform>(child).unwrap(),
622            &offset_global_transform(5.5),
623            "The global transform of an orphaned entity wasn't updated properly",
624        );
625    }
626
627    #[test]
628    fn did_propagate() {
629        ComputeTaskPool::get_or_init(TaskPool::default);
630        let mut world = World::default();
631
632        let mut schedule = Schedule::default();
633        schedule.add_systems(
634            (
635                mark_dirty_trees,
636                sync_simple_transforms,
637                propagate_parent_transforms,
638            )
639                .chain(),
640        );
641
642        // Root entity
643        world.spawn(Transform::from_xyz(1.0, 0.0, 0.0));
644
645        let mut children = Vec::new();
646        world
647            .spawn(Transform::from_xyz(1.0, 0.0, 0.0))
648            .with_children(|parent| {
649                children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.)).id());
650                children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.)).id());
651            });
652        schedule.run(&mut world);
653
654        assert_eq!(
655            *world.get::<GlobalTransform>(children[0]).unwrap(),
656            GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
657        );
658
659        assert_eq!(
660            *world.get::<GlobalTransform>(children[1]).unwrap(),
661            GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
662        );
663    }
664
665    #[test]
666    fn did_propagate_command_buffer() {
667        let mut world = World::default();
668
669        let mut schedule = Schedule::default();
670        schedule.add_systems(
671            (
672                mark_dirty_trees,
673                sync_simple_transforms,
674                propagate_parent_transforms,
675            )
676                .chain(),
677        );
678
679        // Root entity
680        let mut queue = CommandQueue::default();
681        let mut commands = Commands::new(&mut queue, &world);
682        let mut children = Vec::new();
683        commands
684            .spawn(Transform::from_xyz(1.0, 0.0, 0.0))
685            .with_children(|parent| {
686                children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
687                children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.0)).id());
688            });
689        queue.apply(&mut world);
690        schedule.run(&mut world);
691
692        assert_eq!(
693            *world.get::<GlobalTransform>(children[0]).unwrap(),
694            GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
695        );
696
697        assert_eq!(
698            *world.get::<GlobalTransform>(children[1]).unwrap(),
699            GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
700        );
701    }
702
703    #[test]
704    fn correct_children() {
705        ComputeTaskPool::get_or_init(TaskPool::default);
706        let mut world = World::default();
707
708        let mut schedule = Schedule::default();
709        schedule.add_systems(
710            (
711                mark_dirty_trees,
712                sync_simple_transforms,
713                propagate_parent_transforms,
714            )
715                .chain(),
716        );
717
718        // Add parent entities
719        let mut children = Vec::new();
720        let parent = {
721            let mut command_queue = CommandQueue::default();
722            let mut commands = Commands::new(&mut command_queue, &world);
723            let parent = commands.spawn(Transform::from_xyz(1.0, 0.0, 0.0)).id();
724            commands.entity(parent).with_children(|parent| {
725                children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
726                children.push(parent.spawn(Transform::from_xyz(0.0, 3.0, 0.0)).id());
727            });
728            command_queue.apply(&mut world);
729            schedule.run(&mut world);
730            parent
731        };
732
733        assert_eq!(
734            world
735                .get::<Children>(parent)
736                .unwrap()
737                .iter()
738                .collect::<Vec<_>>(),
739            children,
740        );
741
742        // Parent `e1` to `e2`.
743        {
744            let mut command_queue = CommandQueue::default();
745            let mut commands = Commands::new(&mut command_queue, &world);
746            commands.entity(children[1]).add_child(children[0]);
747            command_queue.apply(&mut world);
748            schedule.run(&mut world);
749        }
750
751        assert_eq!(
752            world
753                .get::<Children>(parent)
754                .unwrap()
755                .iter()
756                .collect::<Vec<_>>(),
757            vec![children[1]]
758        );
759
760        assert_eq!(
761            world
762                .get::<Children>(children[1])
763                .unwrap()
764                .iter()
765                .collect::<Vec<_>>(),
766            vec![children[0]]
767        );
768
769        assert!(world.despawn(children[0]));
770
771        schedule.run(&mut world);
772
773        assert_eq!(
774            world
775                .get::<Children>(parent)
776                .unwrap()
777                .iter()
778                .collect::<Vec<_>>(),
779            vec![children[1]]
780        );
781    }
782
783    #[test]
784    fn correct_transforms_when_no_children() {
785        let mut app = App::new();
786        ComputeTaskPool::get_or_init(TaskPool::default);
787
788        app.add_systems(
789            Update,
790            (
791                mark_dirty_trees,
792                sync_simple_transforms,
793                propagate_parent_transforms,
794            )
795                .chain(),
796        );
797
798        let translation = vec3(1.0, 0.0, 0.0);
799
800        // These will be overwritten.
801        let mut child = Entity::from_raw(0);
802        let mut grandchild = Entity::from_raw(1);
803        let parent = app
804            .world_mut()
805            .spawn(Transform::from_translation(translation))
806            .with_children(|builder| {
807                child = builder
808                    .spawn(Transform::IDENTITY)
809                    .with_children(|builder| {
810                        grandchild = builder.spawn(Transform::IDENTITY).id();
811                    })
812                    .id();
813            })
814            .id();
815
816        app.update();
817
818        // check the `Children` structure is spawned
819        assert_eq!(&**app.world().get::<Children>(parent).unwrap(), &[child]);
820        assert_eq!(
821            &**app.world().get::<Children>(child).unwrap(),
822            &[grandchild]
823        );
824        // Note that at this point, the `GlobalTransform`s will not have updated yet, due to
825        // `Commands` delay
826        app.update();
827
828        let mut state = app.world_mut().query::<&GlobalTransform>();
829        for global in state.iter(app.world()) {
830            assert_eq!(global, &GlobalTransform::from_translation(translation));
831        }
832    }
833
834    #[test]
835    #[should_panic]
836    fn panic_when_hierarchy_cycle() {
837        ComputeTaskPool::get_or_init(TaskPool::default);
838        // We cannot directly edit ChildOf and Children, so we use a temp world to break the
839        // hierarchy's invariants.
840        let mut temp = World::new();
841        let mut app = App::new();
842
843        app.add_systems(
844            Update,
845            // It is unsound for this unsafe system to encounter a cycle without panicking. This
846            // requirement only applies to systems with unsafe parallel traversal that result in
847            // aliased mutability during a cycle.
848            propagate_parent_transforms,
849        );
850
851        fn setup_world(world: &mut World) -> (Entity, Entity) {
852            let mut grandchild = Entity::from_raw(0);
853            let child = world
854                .spawn(Transform::IDENTITY)
855                .with_children(|builder| {
856                    grandchild = builder.spawn(Transform::IDENTITY).id();
857                })
858                .id();
859            (child, grandchild)
860        }
861
862        let (temp_child, temp_grandchild) = setup_world(&mut temp);
863        let (child, grandchild) = setup_world(app.world_mut());
864
865        assert_eq!(temp_child, child);
866        assert_eq!(temp_grandchild, grandchild);
867
868        app.world_mut()
869            .spawn(Transform::IDENTITY)
870            .add_children(&[child]);
871
872        let mut child_entity = app.world_mut().entity_mut(child);
873
874        let mut grandchild_entity = temp.entity_mut(grandchild);
875
876        #[expect(
877            unsafe_code,
878            reason = "ChildOf is not mutable but this is for a test to produce a scenario that cannot happen"
879        )]
880        // SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that
881        // cannot happen
882        let mut a = unsafe { child_entity.get_mut_assume_mutable::<ChildOf>().unwrap() };
883
884        // SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that
885        // cannot happen
886        #[expect(
887            unsafe_code,
888            reason = "ChildOf is not mutable but this is for a test to produce a scenario that cannot happen"
889        )]
890        let mut b = unsafe {
891            grandchild_entity
892                .get_mut_assume_mutable::<ChildOf>()
893                .unwrap()
894        };
895
896        core::mem::swap(a.as_mut(), b.as_mut());
897
898        app.update();
899    }
900
901    #[test]
902    fn global_transform_should_not_be_overwritten_after_reparenting() {
903        let translation = Vec3::ONE;
904        let mut world = World::new();
905
906        // Create transform propagation schedule
907        let mut schedule = Schedule::default();
908        schedule.add_systems(
909            (
910                mark_dirty_trees,
911                propagate_parent_transforms,
912                sync_simple_transforms,
913            )
914                .chain(),
915        );
916
917        // Spawn a `Transform` entity with a local translation of `Vec3::ONE`
918        let mut spawn_transform_bundle =
919            || world.spawn(Transform::from_translation(translation)).id();
920
921        // Spawn parent and child with identical transform bundles
922        let parent = spawn_transform_bundle();
923        let child = spawn_transform_bundle();
924        world.entity_mut(parent).add_child(child);
925
926        // Run schedule to propagate transforms
927        schedule.run(&mut world);
928
929        // Child should be positioned relative to its parent
930        let parent_global_transform = *world.entity(parent).get::<GlobalTransform>().unwrap();
931        let child_global_transform = *world.entity(child).get::<GlobalTransform>().unwrap();
932        assert!(parent_global_transform
933            .translation()
934            .abs_diff_eq(translation, 0.1));
935        assert!(child_global_transform
936            .translation()
937            .abs_diff_eq(2. * translation, 0.1));
938
939        // Reparent child
940        world.entity_mut(child).remove::<ChildOf>();
941        world.entity_mut(parent).add_child(child);
942
943        // Run schedule to propagate transforms
944        schedule.run(&mut world);
945
946        // Translations should be unchanged after update
947        assert_eq!(
948            parent_global_transform,
949            *world.entity(parent).get::<GlobalTransform>().unwrap()
950        );
951        assert_eq!(
952            child_global_transform,
953            *world.entity(child).get::<GlobalTransform>().unwrap()
954        );
955    }
956}