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