Skip to main content

bevy_transform/
systems.rs

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