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()).matched();
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, 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 changed entities directly to the consumer as well; we do this to
266            // start feeding it work as soon as possible. The traversal worker should skip sending
267            // these leaves to the consumer because they have already been sent here.
268            let mut producer = move || {
269                for entity in orphaned.read() {
270                    let _ = consumer_tx.send_blocking(entity);
271                }
272                // Changed<> table scans are slow, so we parallelize them to improve performance.
273                changed.par_iter().for_each_init(
274                    || (traversal_tx.clone(), consumer_tx.clone()),
275                    |(traversal_tx, consumer_tx), entity| {
276                        let _ = traversal_tx.send_blocking(entity);
277                        let _ = consumer_tx.send_blocking(entity);
278                    },
279                );
280            };
281            #[cfg(feature = "trace")]
282            info_span!("producer_mark_dirty").in_scope(&mut producer);
283            #[cfg(not(feature = "trace"))]
284            producer();
285        });
286
287        // Merge thread-local bitsets into the shared bitset, growing it to accommodate the largest
288        // entity index we have encountered so far. At steady-state, these local bitsets stay empty.
289        for local_bitset in local_bitset.iter_mut() {
290            if local_bitset.is_empty() {
291                continue;
292            }
293            if local_bitset.len() > shared_bitset.len() {
294                shared_bitset.resize_with(local_bitset.len(), Default::default);
295            }
296            local_bitset.clear();
297        }
298
299        // Reset the bitset for the next frame while preserving the `Vec` length. Using `clear()`
300        // would shrink the length to 0 and force every entity through the overflow path next frame.
301        for w in shared_bitset.iter() {
302            w.store(0, Ordering::Relaxed);
303        }
304    }
305}
306
307// TODO: This serial implementation isn't actually serial, it parallelizes across the roots.
308// Additionally, this couples "no_std" with "single_threaded" when these two features should be
309// independent.
310//
311// What we want to do in a future refactor is take the current "single threaded" implementation, and
312// actually make it single threaded. This will remove any overhead associated with working on a task
313// pool when you only have a single thread, and will have the benefit of removing the need for any
314// unsafe. We would then make the multithreaded implementation work across std and no_std, but this
315// is blocked a no_std compatible Channel, which is why this TODO is not yet implemented.
316//
317// This complexity might also not be needed. If the multithreaded implementation on a single thread
318// is as fast as the single threaded implementation, we could simply remove the entire serial
319// module, and make the multithreaded module no_std compatible.
320//
321/// Serial hierarchy traversal. Useful in `no_std` or single threaded contexts.
322#[cfg(not(feature = "multi_threaded"))]
323mod serial {
324    use crate::prelude::*;
325    use alloc::vec::Vec;
326    use bevy_ecs::prelude::*;
327
328    /// Update [`GlobalTransform`] component of entities based on entity hierarchy and [`Transform`]
329    /// component.
330    ///
331    /// Third party plugins should ensure that this is used in concert with
332    /// [`sync_simple_transforms`](super::sync_simple_transforms) and
333    /// [`mark_dirty_trees`](super::mark_dirty_trees).
334    pub fn propagate_parent_transforms(
335        mut root_query: Query<
336            (Entity, &Children, Ref<Transform>, &mut GlobalTransform),
337            Without<ChildOf>,
338        >,
339        mut orphaned: RemovedComponents<ChildOf>,
340        transform_query: Query<
341            (Ref<Transform>, &mut GlobalTransform, Option<&Children>),
342            With<ChildOf>,
343        >,
344        child_query: Query<(Entity, Ref<ChildOf>), With<GlobalTransform>>,
345        mut orphaned_entities: Local<Vec<Entity>>,
346    ) {
347        orphaned_entities.clear();
348        orphaned_entities.extend(orphaned.read());
349        orphaned_entities.sort_unstable();
350        root_query.par_iter_mut().for_each(
351        |(entity, children, transform, mut global_transform)| {
352            let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
353            if changed {
354                *global_transform = GlobalTransform::from(*transform);
355            }
356
357            for (child, child_of) in child_query.iter_many(children).matched() {
358                assert_eq!(
359                    child_of.parent(), entity,
360                    "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
361                );
362                // SAFETY:
363                // - `child` must have consistent parentage, or the above assertion would panic.
364                //   Since `child` is parented to a root entity, the entire hierarchy leading to it
365                //   is consistent.
366                // - We may operate as if all descendants are consistent, since
367                //   `propagate_recursive` will panic before continuing to propagate if it
368                //   encounters an entity with inconsistent parentage.
369                // - Since each root entity is unique and the hierarchy is consistent and
370                //   forest-like, other root entities' `propagate_recursive` calls will not conflict
371                //   with this one.
372                // - Since this is the only place where `transform_query` gets used, there will be
373                //   no conflicting fetches elsewhere.
374                #[expect(unsafe_code, reason = "`propagate_recursive()` is unsafe due to its use of `Query::get_unchecked()`.")]
375                unsafe {
376                    propagate_recursive(
377                        &global_transform,
378                        &transform_query,
379                        &child_query,
380                        child,
381                        changed || child_of.is_changed(),
382                    );
383                }
384            }
385        },
386    );
387    }
388
389    /// Recursively propagates the transforms for `entity` and all of its descendants.
390    ///
391    /// # Panics
392    ///
393    /// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before
394    /// propagating the transforms of any malformed entities and their descendants.
395    ///
396    /// # Safety
397    ///
398    /// - While this function is running, `transform_query` must not have any fetches for `entity`,
399    ///   nor any of its descendants.
400    /// - The caller must ensure that the hierarchy leading to `entity` is well-formed and must
401    ///   remain as a tree or a forest. Each entity must have at most one parent.
402    #[expect(
403        unsafe_code,
404        reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met."
405    )]
406    unsafe fn propagate_recursive(
407        parent: &GlobalTransform,
408        transform_query: &Query<
409            (Ref<Transform>, &mut GlobalTransform, Option<&Children>),
410            With<ChildOf>,
411        >,
412        child_query: &Query<(Entity, Ref<ChildOf>), With<GlobalTransform>>,
413        entity: Entity,
414        mut changed: bool,
415    ) {
416        let (global_matrix, children) = {
417            let Ok((transform, mut global_transform, children)) =
418            // SAFETY: This call cannot create aliased mutable references.
419            //   - The top level iteration parallelizes on the roots of the hierarchy.
420            //   - The caller ensures that each child has one and only one unique parent throughout
421            //     the entire hierarchy.
422            //
423            // For example, consider the following malformed hierarchy:
424            //
425            //     A
426            //   /   \
427            //  B     C
428            //   \   /
429            //     D
430            //
431            // D has two parents, B and C. If the propagation passes through C, but the ChildOf
432            // component on D points to B, the above check will panic as the origin parent does
433            // match the recorded parent.
434            //
435            // Also consider the following case, where A and B are roots:
436            //
437            //  A       B
438            //   \     /
439            //    C   D
440            //     \ /
441            //      E
442            //
443            // Even if these A and B start two separate tasks running in parallel, one of them will
444            // panic before attempting to mutably access E.
445            (unsafe { transform_query.get_unchecked(entity) }) else {
446                return;
447            };
448
449            changed |= transform.is_changed() || global_transform.is_added();
450            if changed {
451                *global_transform = parent.mul_transform(*transform);
452            }
453            (global_transform, children)
454        };
455
456        let Some(children) = children else { return };
457        for (child, child_of) in child_query.iter_many(children).matched() {
458            assert_eq!(
459            child_of.parent(), entity,
460            "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
461        );
462            // SAFETY: The caller guarantees that `transform_query` will not be fetched for any
463            // descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
464            //
465            // The above assertion ensures that each child has one and only one unique parent
466            // throughout the entire hierarchy.
467            unsafe {
468                propagate_recursive(
469                    global_matrix.as_ref(),
470                    transform_query,
471                    child_query,
472                    child,
473                    changed || child_of.is_changed(),
474                );
475            }
476        }
477    }
478}
479
480// TODO: Relies on `std` until a `no_std` `mpsc` channel is available.
481//
482/// Parallel hierarchy traversal with a batched work sharing scheduler. Often 2-5 times faster than
483/// the serial version.
484#[cfg(feature = "multi_threaded")]
485mod parallel {
486    use crate::prelude::*;
487    // TODO: this implementation could be used in no_std if there are equivalents of these.
488    use crate::systems::StaticTransformOptimizations;
489    use alloc::{sync::Arc, vec::Vec};
490    use bevy_ecs::{entity::UniqueEntitySlice, prelude::*, system::lifetimeless::Read};
491    use bevy_tasks::{ComputeTaskPool, TaskPool};
492    use bevy_utils::Parallel;
493    use core::sync::atomic::{AtomicI32, Ordering};
494    use std::sync::{
495        mpsc::{Receiver, Sender},
496        Mutex,
497    };
498
499    /// Update [`GlobalTransform`] component of entities based on entity hierarchy and [`Transform`]
500    /// component.
501    ///
502    /// Third party plugins should ensure that this is used in concert with
503    /// [`sync_simple_transforms`](super::sync_simple_transforms) and
504    /// [`mark_dirty_trees`](super::mark_dirty_trees).
505    pub fn propagate_parent_transforms(
506        mut queue: Local<WorkQueue>,
507        mut roots: Query<
508            (
509                Entity,
510                Ref<Transform>,
511                &mut GlobalTransform,
512                &Children,
513                Ref<TransformTreeChanged>,
514            ),
515            Without<ChildOf>,
516        >,
517        nodes: NodeQuery,
518        static_optimizations: Res<StaticTransformOptimizations>,
519    ) {
520        // Process roots in parallel, seeding the work queue
521        roots.par_iter_mut().for_each_init(
522            || queue.local_queue.borrow_local_mut(),
523            |outbox, (parent, transform, mut parent_transform, children, transform_tree)| {
524                if static_optimizations.is_enabled() && !transform_tree.is_changed() {
525                    // Early exit if the subtree is static and the optimization is enabled.
526                    return;
527                }
528
529                *parent_transform = GlobalTransform::from(*transform);
530
531                // SAFETY: the parent entities passed into this function are taken from iterating
532                // over the root entity query. Queries iterate over disjoint entities, preventing
533                // mutable aliasing, and making this call safe.
534                #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
535                unsafe {
536                    propagate_descendants_unchecked(
537                        parent,
538                        parent_transform,
539                        children,
540                        &nodes,
541                        outbox,
542                        &queue,
543                        &static_optimizations,
544                        // Need to revisit this single-max-depth by profiling more representative
545                        // scenes. It's possible that it is actually beneficial to go deep into the
546                        // hierarchy to build up a good task queue before starting the workers.
547                        // However, we avoid this for now to prevent cases where only a single
548                        // thread is going deep into the hierarchy while the others sit idle, which
549                        // is the problem that the tasks sharing workers already solve.
550                        1,
551                    );
552                }
553            },
554        );
555        // Send all tasks in thread local outboxes *after* roots are processed to reduce the total
556        // number of channel sends by avoiding sending partial batches.
557        queue.send_batches();
558
559        if let Ok(rx) = queue.receiver.try_lock() {
560            if let Some(task) = rx.try_iter().next() {
561                // This is a bit silly, but the only way to see if there is any work is to grab a
562                // task. Peeking will remove the task even if you don't call `next`, resulting in
563                // dropping a task. What we do here is grab the first task if there is one, then
564                // immediately send it to the back of the queue.
565                queue.sender.send(task).ok();
566            } else {
567                return; // No work, don't bother spawning any tasks
568            }
569        }
570
571        // Spawn workers on the task pool to recursively propagate the hierarchy in parallel.
572        let task_pool = ComputeTaskPool::get_or_init(TaskPool::default);
573        task_pool.scope(|s| {
574            (1..task_pool.thread_num()) // First worker is run locally instead of the task pool.
575                .for_each(|_| {
576                    s.spawn(async { propagation_worker(&queue, &nodes, &static_optimizations) });
577                });
578            propagation_worker(&queue, &nodes, &static_optimizations);
579        });
580    }
581
582    /// A parallel worker that will consume processed parent entities from the queue, and push
583    /// children to the queue once it has propagated their [`GlobalTransform`].
584    #[inline]
585    fn propagation_worker(
586        queue: &WorkQueue,
587        nodes: &NodeQuery,
588        static_optimizations: &StaticTransformOptimizations,
589    ) {
590        #[cfg(feature = "trace")]
591        let _span = tracing::info_span!("transform propagation worker").entered();
592
593        let mut outbox = queue.local_queue.borrow_local_mut();
594        loop {
595            // Try to acquire a lock on the work queue in a tight loop. Profiling shows this is much
596            // more efficient than relying on `.lock()`, which causes gaps to form between tasks.
597            let Ok(rx) = queue.receiver.try_lock() else {
598                core::hint::spin_loop(); // No apparent impact on profiles, but best practice.
599                continue;
600            };
601            // If the queue is empty and no other threads are busy processing work, we can conclude
602            // there is no more work to do, and end the task by exiting the loop.
603            let Some(mut tasks) = rx.try_iter().next() else {
604                if queue.busy_threads.load(Ordering::Relaxed) == 0 {
605                    break; // All work is complete, kill the worker
606                }
607                continue; // No work to do now, but another thread is busy creating more work.
608            };
609            if tasks.is_empty() {
610                continue; // This shouldn't happen, but if it does, we might as well stop early.
611            }
612
613            // If the task queue is extremely short, it's worthwhile to gather a few more tasks to
614            // reduce the amount of thread synchronization needed once this very short task is
615            // complete.
616            while tasks.len() < WorkQueue::CHUNK_SIZE / 2 {
617                let Some(mut extra_task) = rx.try_iter().next() else {
618                    break;
619                };
620                tasks.append(&mut extra_task);
621            }
622
623            // At this point, we know there is work to do, so we increment the busy thread counter,
624            // and drop the mutex guard *after* we have incremented the counter. This ensures that
625            // if another thread is able to acquire a lock, the busy thread counter will already be
626            // incremented.
627            queue.busy_threads.fetch_add(1, Ordering::Relaxed);
628            drop(rx); // Important: drop after atomic and before work starts.
629
630            for parent in tasks.drain(..) {
631                // SAFETY: each task pushed to the worker queue represents an unprocessed subtree of
632                // the hierarchy, guaranteeing unique access.
633                #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
634                unsafe {
635                    let (_, (_, p_global_transform, _), (p_children, _)) =
636                        nodes.get_unchecked(parent).unwrap();
637                    propagate_descendants_unchecked(
638                        parent,
639                        p_global_transform,
640                        p_children.unwrap(), // All entities in the queue should have children
641                        nodes,
642                        &mut outbox,
643                        queue,
644                        static_optimizations,
645                        // Only affects performance. Trees deeper than this will still be fully
646                        // propagated, but the work will be broken into multiple tasks. This number
647                        // was chosen to be larger than any reasonable tree depth, while not being
648                        // so large the function could hang on a deep hierarchy.
649                        10_000,
650                    );
651                }
652            }
653            WorkQueue::send_batches_with(&queue.sender, &mut outbox);
654            queue.busy_threads.fetch_add(-1, Ordering::Relaxed);
655        }
656    }
657
658    /// Propagate transforms from `parent` to its `children`, pushing updated child entities to the
659    /// `outbox`. This function will continue propagating transforms to descendants in a depth-first
660    /// traversal, while simultaneously pushing unvisited branches to the outbox, for other threads
661    /// to take when idle.
662    ///
663    /// # Safety
664    ///
665    /// Callers must ensure that concurrent calls to this function are given unique `parent`
666    /// entities. Calling this function concurrently with the same `parent` is unsound. This
667    /// function will validate that the entity hierarchy does not contain cycles to prevent mutable
668    /// aliasing during propagation, but it is unable to verify that it isn't being used to mutably
669    /// alias the same entity.
670    ///
671    /// ## Panics
672    ///
673    /// Panics if the parent of a child node is not the same as the supplied `parent`. This
674    /// assertion ensures that the hierarchy is acyclic, which in turn ensures that if the caller is
675    /// following the supplied safety rules, multi-threaded propagation is sound.
676    #[inline]
677    #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
678    unsafe fn propagate_descendants_unchecked(
679        parent: Entity,
680        p_global_transform: Mut<GlobalTransform>,
681        p_children: &Children,
682        nodes: &NodeQuery,
683        outbox: &mut Vec<Entity>,
684        queue: &WorkQueue,
685        static_optimizations: &StaticTransformOptimizations,
686        max_depth: usize,
687    ) {
688        // Create mutable copies of the input variables, used for iterative depth-first traversal.
689        let (mut parent, mut p_global_transform, mut p_children) =
690            (parent, p_global_transform, p_children);
691
692        // See the optimization note at the end to understand why this loop is here.
693        for depth in 1..=max_depth {
694            // Safety: traversing the entity tree from the roots, we assert that the childof and
695            // children pointers match in both directions (see assert below) to ensure the hierarchy
696            // does not have any cycles. Because the hierarchy does not have cycles, we know we are
697            // visiting disjoint entities in parallel, which is safe.
698            #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
699            let children_iter = unsafe {
700                nodes.iter_many_unique_unsafe(UniqueEntitySlice::from_slice_unchecked(p_children))
701            }
702            .matched();
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::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
1222    // Regression test for https://github.com/bevyengine/bevy/issues/25416
1223    #[test]
1224    fn did_propagate_after_entity_index_reuse() {
1225        // Enough same-frame removals to flush full chunks of stale entity IDs through
1226        // `mark_dirty_trees` before the live replacements are processed.
1227        const ROOT_COUNT: usize = 256;
1228        const CHILDREN_PER_ROOT: usize = 8;
1229
1230        ComputeTaskPool::get_or_init(TaskPool::default);
1231        let mut world = World::default();
1232
1233        let mut schedule = Schedule::default();
1234        schedule.add_systems(
1235            (
1236                mark_dirty_trees,
1237                sync_simple_transforms,
1238                propagate_parent_transforms,
1239            )
1240                .chain(),
1241        );
1242        world.insert_resource(StaticTransformOptimizations::default());
1243
1244        let roots: Vec<Entity> = (0..ROOT_COUNT)
1245            .map(|i| {
1246                world
1247                    .spawn(Transform::from_xyz(10.0 + i as f32, 0.0, 0.0))
1248                    .id()
1249            })
1250            .collect();
1251        let spawn_children = |world: &mut World| {
1252            for &root in &roots {
1253                for slot in 0..CHILDREN_PER_ROOT {
1254                    world.spawn((
1255                        Transform::from_xyz(0.0, 1.0 + slot as f32, 0.0),
1256                        ChildOf(root),
1257                    ));
1258                }
1259            }
1260        };
1261        spawn_children(&mut world);
1262        schedule.run(&mut world);
1263
1264        // Despawn every child and spawn replacements in the same frame, reusing the freed
1265        // entity indices.
1266        let stale: Vec<Entity> = roots
1267            .iter()
1268            .flat_map(|&root| world.get::<Children>(root).unwrap().iter())
1269            .collect();
1270        for child in stale {
1271            world.despawn(child);
1272        }
1273        spawn_children(&mut world);
1274        schedule.run(&mut world);
1275
1276        let mut children = world.query::<(&Transform, &GlobalTransform, &ChildOf)>();
1277        for (transform, global_transform, child_of) in children.iter(&world) {
1278            let root_transform = world.get::<Transform>(child_of.parent()).unwrap();
1279            assert_eq!(
1280                global_transform,
1281                &(GlobalTransform::from(*root_transform) * *transform),
1282                "respawned child kept a stale GlobalTransform",
1283            );
1284        }
1285    }
1286}