bevy/crates/bevy_transform/src/systems.rs
Emerson Coskey b04947d44f
Migrate bevy_transform to required components (#14964)
The first step in the migration to required components! This PR removes
`GlobalTransform` from all user-facing code, since it's now added
automatically wherever `Transform` is used.

## Testing

- None of the examples I tested were broken, and I assume breaking
transforms in any way would be visible *everywhere*

---

## Changelog

- Make `Transform` require `GlobalTransform`
~~- Remove `GlobalTransform` from all engine bundles~~
- Remove in-engine insertions of GlobalTransform and TransformBundle
- Deprecate `TransformBundle`
- update docs to reflect changes

## Migration Guide

Replace all insertions of `GlobalTransform` and/or `TransformBundle`
with `Transform` alone.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Tim <JustTheCoolDude@gmail.com>
2024-09-27 17:06:48 +00:00

518 lines
19 KiB
Rust

use crate::components::{GlobalTransform, Transform};
use bevy_ecs::{
change_detection::Ref,
prelude::{Changed, DetectChanges, Entity, Query, With, Without},
query::{Added, Or},
removal_detection::RemovedComponents,
system::{Local, ParamSet},
};
use bevy_hierarchy::{Children, Parent};
/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy
///
/// Third party plugins should ensure that this is used in concert with [`propagate_transforms`].
pub fn sync_simple_transforms(
mut query: ParamSet<(
Query<
(&Transform, &mut GlobalTransform),
(
Or<(Changed<Transform>, Added<GlobalTransform>)>,
Without<Parent>,
Without<Children>,
),
>,
Query<(Ref<Transform>, &mut GlobalTransform), (Without<Parent>, Without<Children>)>,
)>,
mut orphaned: RemovedComponents<Parent>,
) {
// Update changed entities.
query
.p0()
.par_iter_mut()
.for_each(|(transform, mut global_transform)| {
*global_transform = GlobalTransform::from(*transform);
});
// Update orphaned entities.
let mut query = query.p1();
let mut iter = query.iter_many_mut(orphaned.read());
while let Some((transform, mut global_transform)) = iter.fetch_next() {
if !transform.is_changed() && !global_transform.is_added() {
*global_transform = GlobalTransform::from(*transform);
}
}
}
/// Update [`GlobalTransform`] component of entities based on entity hierarchy and
/// [`Transform`] component.
///
/// Third party plugins should ensure that this is used in concert with [`sync_simple_transforms`].
pub fn propagate_transforms(
mut root_query: Query<
(Entity, &Children, Ref<Transform>, &mut GlobalTransform),
Without<Parent>,
>,
mut orphaned: RemovedComponents<Parent>,
transform_query: Query<(Ref<Transform>, &mut GlobalTransform, Option<&Children>), With<Parent>>,
parent_query: Query<(Entity, Ref<Parent>), With<GlobalTransform>>,
mut orphaned_entities: Local<Vec<Entity>>,
) {
orphaned_entities.clear();
orphaned_entities.extend(orphaned.read());
orphaned_entities.sort_unstable();
root_query.par_iter_mut().for_each(
|(entity, children, transform, mut global_transform)| {
let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
if changed {
*global_transform = GlobalTransform::from(*transform);
}
for (child, actual_parent) in parent_query.iter_many(children) {
assert_eq!(
actual_parent.get(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
// SAFETY:
// - `child` must have consistent parentage, or the above assertion would panic.
// Since `child` is parented to a root entity, the entire hierarchy leading to it is consistent.
// - We may operate as if all descendants are consistent, since `propagate_recursive` will panic before
// continuing to propagate if it encounters an entity with inconsistent parentage.
// - Since each root entity is unique and the hierarchy is consistent and forest-like,
// other root entities' `propagate_recursive` calls will not conflict with this one.
// - Since this is the only place where `transform_query` gets used, there will be no conflicting fetches elsewhere.
#[expect(unsafe_code, reason = "`propagate_recursive()` is unsafe due to its use of `Query::get_unchecked()`.")]
unsafe {
propagate_recursive(
&global_transform,
&transform_query,
&parent_query,
child,
changed || actual_parent.is_changed(),
);
}
}
},
);
}
/// Recursively propagates the transforms for `entity` and all of its descendants.
///
/// # Panics
///
/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before propagating
/// the transforms of any malformed entities and their descendants.
///
/// # Safety
///
/// - While this function is running, `transform_query` must not have any fetches for `entity`,
/// nor any of its descendants.
/// - The caller must ensure that the hierarchy leading to `entity`
/// is well-formed and must remain as a tree or a forest. Each entity must have at most one parent.
#[expect(
unsafe_code,
reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met."
)]
unsafe fn propagate_recursive(
parent: &GlobalTransform,
transform_query: &Query<
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
With<Parent>,
>,
parent_query: &Query<(Entity, Ref<Parent>), With<GlobalTransform>>,
entity: Entity,
mut changed: bool,
) {
let (global_matrix, children) = {
let Ok((transform, mut global_transform, children)) =
// SAFETY: This call cannot create aliased mutable references.
// - The top level iteration parallelizes on the roots of the hierarchy.
// - The caller ensures that each child has one and only one unique parent throughout the entire
// hierarchy.
//
// For example, consider the following malformed hierarchy:
//
// A
// / \
// B C
// \ /
// D
//
// D has two parents, B and C. If the propagation passes through C, but the Parent component on D points to B,
// the above check will panic as the origin parent does match the recorded parent.
//
// Also consider the following case, where A and B are roots:
//
// A B
// \ /
// C D
// \ /
// E
//
// Even if these A and B start two separate tasks running in parallel, one of them will panic before attempting
// to mutably access E.
(unsafe { transform_query.get_unchecked(entity) }) else {
return;
};
changed |= transform.is_changed() || global_transform.is_added();
if changed {
*global_transform = parent.mul_transform(*transform);
}
(global_transform, children)
};
let Some(children) = children else { return };
for (child, actual_parent) in parent_query.iter_many(children) {
assert_eq!(
actual_parent.get(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
// SAFETY: The caller guarantees that `transform_query` will not be fetched
// for any descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
//
// The above assertion ensures that each child has one and only one unique parent throughout the
// entire hierarchy.
unsafe {
propagate_recursive(
global_matrix.as_ref(),
transform_query,
parent_query,
child,
changed || actual_parent.is_changed(),
);
}
}
}
#[cfg(test)]
mod test {
use bevy_app::prelude::*;
use bevy_ecs::{prelude::*, world::CommandQueue};
use bevy_math::{vec3, Vec3};
use bevy_tasks::{ComputeTaskPool, TaskPool};
use crate::systems::*;
use bevy_hierarchy::{BuildChildren, ChildBuild};
#[test]
fn correct_parent_removed() {
ComputeTaskPool::get_or_init(TaskPool::default);
let mut world = World::default();
let offset_global_transform =
|offset| GlobalTransform::from(Transform::from_xyz(offset, offset, offset));
let offset_transform = |offset| Transform::from_xyz(offset, offset, offset);
let mut schedule = Schedule::default();
schedule.add_systems((sync_simple_transforms, propagate_transforms));
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, &world);
let root = commands.spawn(offset_transform(3.3)).id();
let parent = commands.spawn(offset_transform(4.4)).id();
let child = commands.spawn(offset_transform(5.5)).id();
commands.entity(parent).set_parent(root);
commands.entity(child).set_parent(parent);
command_queue.apply(&mut world);
schedule.run(&mut world);
assert_eq!(
world.get::<GlobalTransform>(parent).unwrap(),
&offset_global_transform(4.4 + 3.3),
"The transform systems didn't run, ie: `GlobalTransform` wasn't updated",
);
// Remove parent of `parent`
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, &world);
commands.entity(parent).remove_parent();
command_queue.apply(&mut world);
schedule.run(&mut world);
assert_eq!(
world.get::<GlobalTransform>(parent).unwrap(),
&offset_global_transform(4.4),
"The global transform of an orphaned entity wasn't updated properly",
);
// Remove parent of `child`
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, &world);
commands.entity(child).remove_parent();
command_queue.apply(&mut world);
schedule.run(&mut world);
assert_eq!(
world.get::<GlobalTransform>(child).unwrap(),
&offset_global_transform(5.5),
"The global transform of an orphaned entity wasn't updated properly",
);
}
#[test]
fn did_propagate() {
ComputeTaskPool::get_or_init(TaskPool::default);
let mut world = World::default();
let mut schedule = Schedule::default();
schedule.add_systems((sync_simple_transforms, propagate_transforms));
// Root entity
world.spawn(Transform::from_xyz(1.0, 0.0, 0.0));
let mut children = Vec::new();
world
.spawn(Transform::from_xyz(1.0, 0.0, 0.0))
.with_children(|parent| {
children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.)).id());
children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.)).id());
});
schedule.run(&mut world);
assert_eq!(
*world.get::<GlobalTransform>(children[0]).unwrap(),
GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
);
assert_eq!(
*world.get::<GlobalTransform>(children[1]).unwrap(),
GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
);
}
#[test]
fn did_propagate_command_buffer() {
let mut world = World::default();
let mut schedule = Schedule::default();
schedule.add_systems((sync_simple_transforms, propagate_transforms));
// Root entity
let mut queue = CommandQueue::default();
let mut commands = Commands::new(&mut queue, &world);
let mut children = Vec::new();
commands
.spawn(Transform::from_xyz(1.0, 0.0, 0.0))
.with_children(|parent| {
children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.0)).id());
});
queue.apply(&mut world);
schedule.run(&mut world);
assert_eq!(
*world.get::<GlobalTransform>(children[0]).unwrap(),
GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
);
assert_eq!(
*world.get::<GlobalTransform>(children[1]).unwrap(),
GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
);
}
#[test]
fn correct_children() {
ComputeTaskPool::get_or_init(TaskPool::default);
let mut world = World::default();
let mut schedule = Schedule::default();
schedule.add_systems((sync_simple_transforms, propagate_transforms));
// Add parent entities
let mut children = Vec::new();
let parent = {
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, &world);
let parent = commands.spawn(Transform::from_xyz(1.0, 0.0, 0.0)).id();
commands.entity(parent).with_children(|parent| {
children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
children.push(parent.spawn(Transform::from_xyz(0.0, 3.0, 0.0)).id());
});
command_queue.apply(&mut world);
schedule.run(&mut world);
parent
};
assert_eq!(
world
.get::<Children>(parent)
.unwrap()
.iter()
.cloned()
.collect::<Vec<_>>(),
children,
);
// Parent `e1` to `e2`.
{
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, &world);
commands.entity(children[1]).add_child(children[0]);
command_queue.apply(&mut world);
schedule.run(&mut world);
}
assert_eq!(
world
.get::<Children>(parent)
.unwrap()
.iter()
.cloned()
.collect::<Vec<_>>(),
vec![children[1]]
);
assert_eq!(
world
.get::<Children>(children[1])
.unwrap()
.iter()
.cloned()
.collect::<Vec<_>>(),
vec![children[0]]
);
assert!(world.despawn(children[0]));
schedule.run(&mut world);
assert_eq!(
world
.get::<Children>(parent)
.unwrap()
.iter()
.cloned()
.collect::<Vec<_>>(),
vec![children[1]]
);
}
#[test]
fn correct_transforms_when_no_children() {
let mut app = App::new();
ComputeTaskPool::get_or_init(TaskPool::default);
app.add_systems(Update, (sync_simple_transforms, propagate_transforms));
let translation = vec3(1.0, 0.0, 0.0);
// These will be overwritten.
let mut child = Entity::from_raw(0);
let mut grandchild = Entity::from_raw(1);
let parent = app
.world_mut()
.spawn(Transform::from_translation(translation))
.with_children(|builder| {
child = builder
.spawn(Transform::IDENTITY)
.with_children(|builder| {
grandchild = builder.spawn(Transform::IDENTITY).id();
})
.id();
})
.id();
app.update();
// check the `Children` structure is spawned
assert_eq!(&**app.world().get::<Children>(parent).unwrap(), &[child]);
assert_eq!(
&**app.world().get::<Children>(child).unwrap(),
&[grandchild]
);
// Note that at this point, the `GlobalTransform`s will not have updated yet, due to `Commands` delay
app.update();
let mut state = app.world_mut().query::<&GlobalTransform>();
for global in state.iter(app.world()) {
assert_eq!(global, &GlobalTransform::from_translation(translation));
}
}
#[test]
#[should_panic]
fn panic_when_hierarchy_cycle() {
ComputeTaskPool::get_or_init(TaskPool::default);
// We cannot directly edit Parent and Children, so we use a temp world to break
// the hierarchy's invariants.
let mut temp = World::new();
let mut app = App::new();
app.add_systems(Update, (propagate_transforms, sync_simple_transforms));
fn setup_world(world: &mut World) -> (Entity, Entity) {
let mut grandchild = Entity::from_raw(0);
let child = world
.spawn(Transform::IDENTITY)
.with_children(|builder| {
grandchild = builder.spawn(Transform::IDENTITY).id();
})
.id();
(child, grandchild)
}
let (temp_child, temp_grandchild) = setup_world(&mut temp);
let (child, grandchild) = setup_world(app.world_mut());
assert_eq!(temp_child, child);
assert_eq!(temp_grandchild, grandchild);
app.world_mut()
.spawn(Transform::IDENTITY)
.add_children(&[child]);
core::mem::swap(
&mut *app.world_mut().get_mut::<Parent>(child).unwrap(),
&mut *temp.get_mut::<Parent>(grandchild).unwrap(),
);
app.update();
}
#[test]
fn global_transform_should_not_be_overwritten_after_reparenting() {
let translation = Vec3::ONE;
let mut world = World::new();
// Create transform propagation schedule
let mut schedule = Schedule::default();
schedule.add_systems((sync_simple_transforms, propagate_transforms));
// Spawn a `Transform` entity with a local translation of `Vec3::ONE`
let mut spawn_transform_bundle =
|| world.spawn(Transform::from_translation(translation)).id();
// Spawn parent and child with identical transform bundles
let parent = spawn_transform_bundle();
let child = spawn_transform_bundle();
world.entity_mut(parent).add_child(child);
// Run schedule to propagate transforms
schedule.run(&mut world);
// Child should be positioned relative to its parent
let parent_global_transform = *world.entity(parent).get::<GlobalTransform>().unwrap();
let child_global_transform = *world.entity(child).get::<GlobalTransform>().unwrap();
assert!(parent_global_transform
.translation()
.abs_diff_eq(translation, 0.1));
assert!(child_global_transform
.translation()
.abs_diff_eq(2. * translation, 0.1));
// Reparent child
world.entity_mut(child).remove_parent();
world.entity_mut(parent).add_child(child);
// Run schedule to propagate transforms
schedule.run(&mut world);
// Translations should be unchanged after update
assert_eq!(
parent_global_transform,
*world.entity(parent).get::<GlobalTransform>().unwrap()
);
assert_eq!(
child_global_transform,
*world.entity(child).get::<GlobalTransform>().unwrap()
);
}
}