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