bevy_transform/
systems.rs

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