Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_transform/src/systems.rs
6595 views
1
use crate::components::{GlobalTransform, Transform, TransformTreeChanged};
2
use bevy_ecs::prelude::*;
3
#[cfg(feature = "std")]
4
pub use parallel::propagate_parent_transforms;
5
#[cfg(not(feature = "std"))]
6
pub 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`].
12
pub 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.
46
pub 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"))]
88
mod 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")]
250
mod 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)]
557
mod 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_u32(0).unwrap();
802
let mut grandchild = Entity::from_raw_u32(1).unwrap();
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_u32(0).unwrap();
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
}
957
958