use crate::components::{GlobalTransform, Transform}; use bevy_ecs::{ change_detection::Ref, prelude::{Changed, DetectChanges, Entity, Query, With, Without}, }; 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: Query< (&Transform, &mut GlobalTransform), (Changed, Without, Without), >, ) { query .par_iter_mut() .for_each_mut(|(transform, mut global_transform)| { *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, &mut GlobalTransform), Without, >, transform_query: Query<(Ref, &mut GlobalTransform, Option<&Children>), With>, parent_query: Query<(Entity, Ref)>, ) { root_query.par_iter_mut().for_each_mut( |(entity, children, transform, mut global_transform)| { let changed = transform.is_changed(); 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. 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. unsafe fn propagate_recursive( parent: &GlobalTransform, transform_query: &Query< (Ref, &mut GlobalTransform, Option<&Children>), With, >, parent_query: &Query<(Entity, Ref)>, 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(); 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, transform_query, parent_query, child, changed || actual_parent.is_changed(), ); } } } #[cfg(test)] mod test { use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_ecs::system::CommandQueue; use bevy_math::vec3; use bevy_tasks::{ComputeTaskPool, TaskPool}; use crate::components::{GlobalTransform, Transform}; use crate::systems::*; use crate::TransformBundle; use bevy_hierarchy::{BuildChildren, BuildWorldChildren, Children, Parent}; #[test] fn did_propagate() { ComputeTaskPool::init(TaskPool::default); let mut world = World::default(); let mut schedule = Schedule::new(); schedule.add_systems((sync_simple_transforms, propagate_transforms)); // Root entity world.spawn(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))); let mut children = Vec::new(); world .spawn(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))) .with_children(|parent| { children.push( parent .spawn(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.))) .id(), ); children.push( parent .spawn(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.))) .id(), ); }); schedule.run(&mut world); assert_eq!( *world.get::(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::(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::new(); 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(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))) .with_children(|parent| { children.push( parent .spawn(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.0))) .id(), ); children.push( parent .spawn(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.0))) .id(), ); }); queue.apply(&mut world); schedule.run(&mut world); assert_eq!( *world.get::(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::(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::init(TaskPool::default); let mut world = World::default(); let mut schedule = Schedule::new(); 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::(parent) .unwrap() .iter() .cloned() .collect::>(), 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::(parent) .unwrap() .iter() .cloned() .collect::>(), vec![children[1]] ); assert_eq!( world .get::(children[1]) .unwrap() .iter() .cloned() .collect::>(), vec![children[0]] ); assert!(world.despawn(children[0])); schedule.run(&mut world); assert_eq!( world .get::(parent) .unwrap() .iter() .cloned() .collect::>(), vec![children[1]] ); } #[test] fn correct_transforms_when_no_children() { let mut app = App::new(); ComputeTaskPool::init(TaskPool::default); app.add_systems((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 .spawn(( Transform::from_translation(translation), GlobalTransform::IDENTITY, )) .with_children(|builder| { child = builder .spawn(TransformBundle::IDENTITY) .with_children(|builder| { grandchild = builder.spawn(TransformBundle::IDENTITY).id(); }) .id(); }) .id(); app.update(); // check the `Children` structure is spawned assert_eq!(&**app.world.get::(parent).unwrap(), &[child]); assert_eq!(&**app.world.get::(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.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::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((propagate_transforms, sync_simple_transforms)); fn setup_world(world: &mut World) -> (Entity, Entity) { let mut grandchild = Entity::from_raw(0); let child = world .spawn(TransformBundle::IDENTITY) .with_children(|builder| { grandchild = builder.spawn(TransformBundle::IDENTITY).id(); }) .id(); (child, grandchild) } let (temp_child, temp_grandchild) = setup_world(&mut temp); let (child, grandchild) = setup_world(&mut app.world); assert_eq!(temp_child, child); assert_eq!(temp_grandchild, grandchild); app.world .spawn(TransformBundle::IDENTITY) .push_children(&[child]); std::mem::swap( &mut *app.world.get_mut::(child).unwrap(), &mut *temp.get_mut::(grandchild).unwrap(), ); app.update(); } }