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, 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).matched() {
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).matched() {
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 .matched();
704
705 let mut last_child = None;
706 let new_children = children_iter.filter_map(
707 |(child, (transform, mut global_transform, tree), (children, child_of))| {
708 if static_optimizations.is_enabled()
709 && !tree.is_changed()
710 && !p_global_transform.is_changed()
711 {
712 // Static scene optimization
713 return None;
714 }
715 assert_eq!(child_of.parent(), parent);
716
717 // Transform prop is expensive - this helps avoid updating entire subtrees if
718 // the GlobalTransform is unchanged, at the cost of an added equality check.
719 global_transform.set_if_neq(p_global_transform.mul_transform(*transform));
720
721 children.map(|children| {
722 // Only continue propagation if the entity has children.
723 last_child = Some((child, global_transform, children));
724 child
725 })
726 },
727 );
728 outbox.extend(new_children);
729
730 if depth >= max_depth || last_child.is_none() {
731 break; // Don't remove anything from the outbox or send any chunks, just exit.
732 }
733
734 // Optimization: tasks should consume work locally as long as they can to avoid
735 // thread synchronization for as long as possible.
736 if let Some(last_child) = last_child {
737 // Overwrite parent data with children, and loop to iterate through descendants.
738 (parent, p_global_transform, p_children) = last_child;
739 outbox.pop();
740
741 // Send chunks during traversal. This allows sharing tasks with other threads before
742 // fully completing the traversal.
743 if outbox.len() >= WorkQueue::CHUNK_SIZE {
744 WorkQueue::send_batches_with(&queue.sender, outbox);
745 }
746 }
747 }
748 }
749
750 /// Alias for a large, repeatedly used query. Queries for transform entities that have both a
751 /// parent and possibly children, thus they are not roots.
752 type NodeQuery<'w, 's> = Query<
753 'w,
754 's,
755 (
756 Entity,
757 (
758 Ref<'static, Transform>,
759 Mut<'static, GlobalTransform>,
760 Ref<'static, TransformTreeChanged>,
761 ),
762 (Option<Read<Children>>, Read<ChildOf>),
763 ),
764 >;
765
766 /// A queue shared between threads for transform propagation.
767 pub struct WorkQueue {
768 /// A semaphore that tracks how many threads are busy doing work. Used to determine when
769 /// there is no more work to do.
770 busy_threads: AtomicI32,
771 sender: Sender<Vec<Entity>>,
772 receiver: Arc<Mutex<Receiver<Vec<Entity>>>>,
773 local_queue: Parallel<Vec<Entity>>,
774 }
775 impl Default for WorkQueue {
776 fn default() -> Self {
777 let (tx, rx) = std::sync::mpsc::channel();
778 Self {
779 busy_threads: AtomicI32::default(),
780 sender: tx,
781 receiver: Arc::new(Mutex::new(rx)),
782 local_queue: Default::default(),
783 }
784 }
785 }
786 impl WorkQueue {
787 const CHUNK_SIZE: usize = 512;
788
789 #[inline]
790 fn send_batches_with(sender: &Sender<Vec<Entity>>, outbox: &mut Vec<Entity>) {
791 for chunk in outbox
792 .chunks(WorkQueue::CHUNK_SIZE)
793 .filter(|c| !c.is_empty())
794 {
795 sender.send(chunk.to_vec()).ok();
796 }
797 outbox.clear();
798 }
799
800 #[inline]
801 fn send_batches(&mut self) {
802 let Self {
803 sender,
804 local_queue,
805 ..
806 } = self;
807 // Iterate over the locals to send batched tasks, avoiding the need to drain the locals
808 // into a larger allocation.
809 local_queue
810 .iter_mut()
811 .for_each(|outbox| Self::send_batches_with(sender, outbox));
812 }
813 }
814}
815
816#[cfg(test)]
817mod test {
818 use alloc::{vec, vec::Vec};
819 use bevy_app::prelude::*;
820 use bevy_ecs::world::CommandQueue;
821 use bevy_math::{vec3, Vec3};
822 use bevy_tasks::{ComputeTaskPool, TaskPool};
823
824 use crate::systems::*;
825
826 #[test]
827 fn correct_parent_removed() {
828 ComputeTaskPool::get_or_init(TaskPool::default);
829 let mut world = World::default();
830 let offset_global_transform =
831 |offset| GlobalTransform::from(Transform::from_xyz(offset, offset, offset));
832 let offset_transform = |offset| Transform::from_xyz(offset, offset, offset);
833
834 let mut schedule = Schedule::default();
835 schedule.add_systems(
836 (
837 mark_dirty_trees,
838 sync_simple_transforms,
839 propagate_parent_transforms,
840 )
841 .chain(),
842 );
843 world.insert_resource(StaticTransformOptimizations::default());
844
845 let mut command_queue = CommandQueue::default();
846 let mut commands = Commands::new(&mut command_queue, &world);
847 let root = commands.spawn(offset_transform(3.3)).id();
848 let parent = commands.spawn(offset_transform(4.4)).id();
849 let child = commands.spawn(offset_transform(5.5)).id();
850 commands.entity(parent).insert(ChildOf(root));
851 commands.entity(child).insert(ChildOf(parent));
852 command_queue.apply(&mut world);
853 schedule.run(&mut world);
854
855 assert_eq!(
856 world.get::<GlobalTransform>(parent).unwrap(),
857 &offset_global_transform(4.4 + 3.3),
858 "The transform systems didn't run, ie: `GlobalTransform` wasn't updated",
859 );
860
861 // Remove parent of `parent`
862 let mut command_queue = CommandQueue::default();
863 let mut commands = Commands::new(&mut command_queue, &world);
864 commands.entity(parent).remove::<ChildOf>();
865 command_queue.apply(&mut world);
866 schedule.run(&mut world);
867
868 assert_eq!(
869 world.get::<GlobalTransform>(parent).unwrap(),
870 &offset_global_transform(4.4),
871 "The global transform of an orphaned entity wasn't updated properly",
872 );
873
874 // Remove parent of `child`
875 let mut command_queue = CommandQueue::default();
876 let mut commands = Commands::new(&mut command_queue, &world);
877 commands.entity(child).remove::<ChildOf>();
878 command_queue.apply(&mut world);
879 schedule.run(&mut world);
880
881 assert_eq!(
882 world.get::<GlobalTransform>(child).unwrap(),
883 &offset_global_transform(5.5),
884 "The global transform of an orphaned entity wasn't updated properly",
885 );
886 }
887
888 #[test]
889 fn did_propagate() {
890 ComputeTaskPool::get_or_init(TaskPool::default);
891 let mut world = World::default();
892
893 let mut schedule = Schedule::default();
894 schedule.add_systems(
895 (
896 mark_dirty_trees,
897 sync_simple_transforms,
898 propagate_parent_transforms,
899 )
900 .chain(),
901 );
902 world.insert_resource(StaticTransformOptimizations::default());
903
904 // Root entity
905 world.spawn(Transform::from_xyz(1.0, 0.0, 0.0));
906
907 let mut children = Vec::new();
908 world
909 .spawn(Transform::from_xyz(1.0, 0.0, 0.0))
910 .with_children(|parent| {
911 children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.)).id());
912 children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.)).id());
913 });
914 schedule.run(&mut world);
915
916 assert_eq!(
917 *world.get::<GlobalTransform>(children[0]).unwrap(),
918 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
919 );
920
921 assert_eq!(
922 *world.get::<GlobalTransform>(children[1]).unwrap(),
923 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
924 );
925 }
926
927 #[test]
928 fn did_propagate_command_buffer() {
929 let mut world = World::default();
930
931 let mut schedule = Schedule::default();
932 schedule.add_systems(
933 (
934 mark_dirty_trees,
935 sync_simple_transforms,
936 propagate_parent_transforms,
937 )
938 .chain(),
939 );
940 world.insert_resource(StaticTransformOptimizations::default());
941
942 // Root entity
943 let mut queue = CommandQueue::default();
944 let mut commands = Commands::new(&mut queue, &world);
945 let mut children = Vec::new();
946 commands
947 .spawn(Transform::from_xyz(1.0, 0.0, 0.0))
948 .with_children(|parent| {
949 children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
950 children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.0)).id());
951 });
952 queue.apply(&mut world);
953 schedule.run(&mut world);
954
955 assert_eq!(
956 *world.get::<GlobalTransform>(children[0]).unwrap(),
957 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
958 );
959
960 assert_eq!(
961 *world.get::<GlobalTransform>(children[1]).unwrap(),
962 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
963 );
964 }
965
966 #[test]
967 fn correct_children() {
968 ComputeTaskPool::get_or_init(TaskPool::default);
969 let mut world = World::default();
970
971 let mut schedule = Schedule::default();
972 schedule.add_systems(
973 (
974 mark_dirty_trees,
975 sync_simple_transforms,
976 propagate_parent_transforms,
977 )
978 .chain(),
979 );
980 world.insert_resource(StaticTransformOptimizations::default());
981
982 // Add parent entities
983 let mut children = Vec::new();
984 let parent = {
985 let mut command_queue = CommandQueue::default();
986 let mut commands = Commands::new(&mut command_queue, &world);
987 let parent = commands.spawn(Transform::from_xyz(1.0, 0.0, 0.0)).id();
988 commands.entity(parent).with_children(|parent| {
989 children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
990 children.push(parent.spawn(Transform::from_xyz(0.0, 3.0, 0.0)).id());
991 });
992 command_queue.apply(&mut world);
993 schedule.run(&mut world);
994 parent
995 };
996
997 assert_eq!(
998 world
999 .get::<Children>(parent)
1000 .unwrap()
1001 .iter()
1002 .collect::<Vec<_>>(),
1003 children,
1004 );
1005
1006 // Parent `e1` to `e2`.
1007 {
1008 let mut command_queue = CommandQueue::default();
1009 let mut commands = Commands::new(&mut command_queue, &world);
1010 commands.entity(children[1]).add_child(children[0]);
1011 command_queue.apply(&mut world);
1012 schedule.run(&mut world);
1013 }
1014
1015 assert_eq!(
1016 world
1017 .get::<Children>(parent)
1018 .unwrap()
1019 .iter()
1020 .collect::<Vec<_>>(),
1021 vec![children[1]]
1022 );
1023
1024 assert_eq!(
1025 world
1026 .get::<Children>(children[1])
1027 .unwrap()
1028 .iter()
1029 .collect::<Vec<_>>(),
1030 vec![children[0]]
1031 );
1032
1033 assert!(world.despawn(children[0]));
1034
1035 schedule.run(&mut world);
1036
1037 assert_eq!(
1038 world
1039 .get::<Children>(parent)
1040 .unwrap()
1041 .iter()
1042 .collect::<Vec<_>>(),
1043 vec![children[1]]
1044 );
1045 }
1046
1047 #[test]
1048 fn correct_transforms_when_no_children() {
1049 let mut app = App::new();
1050 ComputeTaskPool::get_or_init(TaskPool::default);
1051
1052 app.add_systems(
1053 Update,
1054 (
1055 mark_dirty_trees,
1056 sync_simple_transforms,
1057 propagate_parent_transforms,
1058 )
1059 .chain(),
1060 )
1061 .insert_resource(StaticTransformOptimizations::default());
1062
1063 let translation = vec3(1.0, 0.0, 0.0);
1064
1065 // These will be overwritten.
1066 let mut child = Entity::from_raw_u32(0).unwrap();
1067 let mut grandchild = Entity::from_raw_u32(1).unwrap();
1068 let parent = app
1069 .world_mut()
1070 .spawn(Transform::from_translation(translation))
1071 .with_children(|builder| {
1072 child = builder
1073 .spawn(Transform::IDENTITY)
1074 .with_children(|builder| {
1075 grandchild = builder.spawn(Transform::IDENTITY).id();
1076 })
1077 .id();
1078 })
1079 .id();
1080
1081 app.update();
1082
1083 // check the `Children` structure is spawned
1084 assert_eq!(&**app.world().get::<Children>(parent).unwrap(), &[child]);
1085 assert_eq!(
1086 &**app.world().get::<Children>(child).unwrap(),
1087 &[grandchild]
1088 );
1089 // Note that at this point, the `GlobalTransform`s will not have updated yet, due to
1090 // `Commands` delay
1091 app.update();
1092
1093 let mut state = app.world_mut().query::<&GlobalTransform>();
1094 for global in state.iter(app.world()) {
1095 assert_eq!(global, &GlobalTransform::from_translation(translation));
1096 }
1097 }
1098
1099 #[test]
1100 #[should_panic]
1101 fn panic_when_hierarchy_cycle() {
1102 ComputeTaskPool::get_or_init(TaskPool::default);
1103 // We cannot directly edit ChildOf and Children, so we use a temp world to break the
1104 // hierarchy's invariants.
1105 let mut temp = World::new();
1106 let mut app = App::new();
1107
1108 app.add_systems(
1109 Update,
1110 // It is unsound for this unsafe system to encounter a cycle without panicking. This
1111 // requirement only applies to systems with unsafe parallel traversal that result in
1112 // aliased mutability during a cycle.
1113 propagate_parent_transforms,
1114 );
1115
1116 fn setup_world(world: &mut World) -> (Entity, Entity) {
1117 let mut grandchild = Entity::from_raw_u32(0).unwrap();
1118 let child = world
1119 .spawn(Transform::IDENTITY)
1120 .with_children(|builder| {
1121 grandchild = builder.spawn(Transform::IDENTITY).id();
1122 })
1123 .id();
1124 (child, grandchild)
1125 }
1126
1127 let (temp_child, temp_grandchild) = setup_world(&mut temp);
1128 let (child, grandchild) = setup_world(app.world_mut());
1129
1130 assert_eq!(temp_child, child);
1131 assert_eq!(temp_grandchild, grandchild);
1132
1133 app.world_mut()
1134 .spawn(Transform::IDENTITY)
1135 .add_children(&[child]);
1136
1137 let mut child_entity = app.world_mut().entity_mut(child);
1138
1139 let mut grandchild_entity = temp.entity_mut(grandchild);
1140
1141 #[expect(
1142 unsafe_code,
1143 reason = "ChildOf is not mutable but this is for a test to produce a scenario that cannot happen"
1144 )]
1145 // SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that
1146 // cannot happen
1147 let mut a = unsafe { child_entity.get_mut_assume_mutable::<ChildOf>().unwrap() };
1148
1149 #[expect(
1150 unsafe_code,
1151 reason = "ChildOf is not mutable but this is for a test to produce a scenario that cannot happen"
1152 )]
1153 // SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that
1154 // cannot happen
1155 let mut b = unsafe {
1156 grandchild_entity
1157 .get_mut_assume_mutable::<ChildOf>()
1158 .unwrap()
1159 };
1160
1161 core::mem::swap(a.as_mut(), b.as_mut());
1162
1163 app.update();
1164 }
1165
1166 #[test]
1167 fn global_transform_should_not_be_overwritten_after_reparenting() {
1168 let translation = Vec3::ONE;
1169 let mut world = World::new();
1170
1171 // Create transform propagation schedule
1172 let mut schedule = Schedule::default();
1173 schedule.add_systems(
1174 (
1175 mark_dirty_trees,
1176 propagate_parent_transforms,
1177 sync_simple_transforms,
1178 )
1179 .chain(),
1180 );
1181 world.insert_resource(StaticTransformOptimizations::default());
1182
1183 // Spawn a `Transform` entity with a local translation of `Vec3::ONE`
1184 let mut spawn_transform_bundle =
1185 || world.spawn(Transform::from_translation(translation)).id();
1186
1187 // Spawn parent and child with identical transform bundles
1188 let parent = spawn_transform_bundle();
1189 let child = spawn_transform_bundle();
1190 world.entity_mut(parent).add_child(child);
1191
1192 // Run schedule to propagate transforms
1193 schedule.run(&mut world);
1194
1195 // Child should be positioned relative to its parent
1196 let parent_global_transform = *world.entity(parent).get::<GlobalTransform>().unwrap();
1197 let child_global_transform = *world.entity(child).get::<GlobalTransform>().unwrap();
1198 assert!(parent_global_transform
1199 .translation()
1200 .abs_diff_eq(translation, 0.1));
1201 assert!(child_global_transform
1202 .translation()
1203 .abs_diff_eq(2. * translation, 0.1));
1204
1205 // Reparent child
1206 world.entity_mut(child).remove::<ChildOf>();
1207 world.entity_mut(parent).add_child(child);
1208
1209 // Run schedule to propagate transforms
1210 schedule.run(&mut world);
1211
1212 // Translations should be unchanged after update
1213 assert_eq!(
1214 parent_global_transform,
1215 *world.entity(parent).get::<GlobalTransform>().unwrap()
1216 );
1217 assert_eq!(
1218 child_global_transform,
1219 *world.entity(child).get::<GlobalTransform>().unwrap()
1220 );
1221 }
1222}