 8eb0440f1e
			
		
	
	
		8eb0440f1e
		
	
	
	
	
		
			
			## Objective Implement absolute minimum viable product for the changes proposed in bevyengine/rfcs#53. ## Solution - Remove public mutative access to `Parent` (Children is already publicly read-only). This includes public construction methods like `Copy`, `Clone`, and `Default`. - Remove `PreviousParent` - Remove `parent_update_system` - Update all hierarchy related commands to immediately update both `Parent` and `Children` references. ## Remaining TODOs - [ ] Update documentation for both `Parent` and `Children`. Discourage using `EntityCommands::remove` - [x] Add `HierarchyEvent` to notify listeners of hierarchy updates. This is meant to replace listening on `PreviousParent` ## Followup - These changes should be best moved to the hooks mentioned in #3742. - Backing storage for both might be best moved to indexes mentioned in the same relations.
		
			
				
	
	
		
			376 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			376 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use crate::components::{GlobalTransform, Transform};
 | |
| use bevy_ecs::prelude::{Changed, Entity, Query, With, Without};
 | |
| use bevy_hierarchy::{Children, Parent};
 | |
| 
 | |
| /// Update [`GlobalTransform`] component of entities based on entity hierarchy and
 | |
| /// [`Transform`] component.
 | |
| pub fn transform_propagate_system(
 | |
|     mut root_query: Query<
 | |
|         (
 | |
|             Option<(&Children, Changed<Children>)>,
 | |
|             &Transform,
 | |
|             Changed<Transform>,
 | |
|             &mut GlobalTransform,
 | |
|             Entity,
 | |
|         ),
 | |
|         Without<Parent>,
 | |
|     >,
 | |
|     mut transform_query: Query<(
 | |
|         &Transform,
 | |
|         Changed<Transform>,
 | |
|         &mut GlobalTransform,
 | |
|         &Parent,
 | |
|     )>,
 | |
|     children_query: Query<(&Children, Changed<Children>), (With<Parent>, With<GlobalTransform>)>,
 | |
| ) {
 | |
|     for (children, transform, transform_changed, mut global_transform, entity) in
 | |
|         root_query.iter_mut()
 | |
|     {
 | |
|         let mut changed = transform_changed;
 | |
|         if transform_changed {
 | |
|             *global_transform = GlobalTransform::from(*transform);
 | |
|         }
 | |
| 
 | |
|         if let Some((children, changed_children)) = children {
 | |
|             // If our `Children` has changed, we need to recalculate everything below us
 | |
|             changed |= changed_children;
 | |
|             for child in children.iter() {
 | |
|                 let _ = propagate_recursive(
 | |
|                     &global_transform,
 | |
|                     &mut transform_query,
 | |
|                     &children_query,
 | |
|                     *child,
 | |
|                     entity,
 | |
|                     changed,
 | |
|                 );
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn propagate_recursive(
 | |
|     parent: &GlobalTransform,
 | |
|     transform_query: &mut Query<(
 | |
|         &Transform,
 | |
|         Changed<Transform>,
 | |
|         &mut GlobalTransform,
 | |
|         &Parent,
 | |
|     )>,
 | |
|     children_query: &Query<(&Children, Changed<Children>), (With<Parent>, With<GlobalTransform>)>,
 | |
|     entity: Entity,
 | |
|     expected_parent: Entity,
 | |
|     mut changed: bool,
 | |
|     // We use a result here to use the `?` operator. Ideally we'd use a try block instead
 | |
| ) -> Result<(), ()> {
 | |
|     let global_matrix = {
 | |
|         let (transform, transform_changed, mut global_transform, child_parent) =
 | |
|             transform_query.get_mut(entity).map_err(drop)?;
 | |
|         // Note that for parallelising, this check cannot occur here, since there is an `&mut GlobalTransform` (in global_transform)
 | |
|         assert_eq!(
 | |
|             child_parent.get(), expected_parent,
 | |
|             "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
 | |
|         );
 | |
|         changed |= transform_changed;
 | |
|         if changed {
 | |
|             *global_transform = parent.mul_transform(*transform);
 | |
|         }
 | |
|         *global_transform
 | |
|     };
 | |
| 
 | |
|     let (children, changed_children) = children_query.get(entity).map_err(drop)?;
 | |
|     // If our `Children` has changed, we need to recalculate everything below us
 | |
|     changed |= changed_children;
 | |
|     for child in children.iter() {
 | |
|         let _ = propagate_recursive(
 | |
|             &global_matrix,
 | |
|             transform_query,
 | |
|             children_query,
 | |
|             *child,
 | |
|             entity,
 | |
|             changed,
 | |
|         );
 | |
|     }
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| #[cfg(test)]
 | |
| mod test {
 | |
|     use bevy_app::prelude::*;
 | |
|     use bevy_ecs::prelude::*;
 | |
|     use bevy_ecs::system::CommandQueue;
 | |
|     use bevy_math::vec3;
 | |
| 
 | |
|     use crate::components::{GlobalTransform, Transform};
 | |
|     use crate::systems::transform_propagate_system;
 | |
|     use crate::TransformBundle;
 | |
|     use bevy_hierarchy::{BuildChildren, BuildWorldChildren, Children, Parent};
 | |
| 
 | |
|     #[test]
 | |
|     fn did_propagate() {
 | |
|         let mut world = World::default();
 | |
| 
 | |
|         let mut update_stage = SystemStage::parallel();
 | |
|         update_stage.add_system(transform_propagate_system);
 | |
| 
 | |
|         let mut schedule = Schedule::default();
 | |
|         schedule.add_stage("update", update_stage);
 | |
| 
 | |
|         // Root entity
 | |
|         world
 | |
|             .spawn()
 | |
|             .insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)));
 | |
| 
 | |
|         let mut children = Vec::new();
 | |
|         world
 | |
|             .spawn()
 | |
|             .insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)))
 | |
|             .with_children(|parent| {
 | |
|                 children.push(
 | |
|                     parent
 | |
|                         .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.)))
 | |
|                         .id(),
 | |
|                 );
 | |
|                 children.push(
 | |
|                     parent
 | |
|                         .spawn_bundle(TransformBundle::from(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 update_stage = SystemStage::parallel();
 | |
|         update_stage.add_system(transform_propagate_system);
 | |
| 
 | |
|         let mut schedule = Schedule::default();
 | |
|         schedule.add_stage("update", update_stage);
 | |
| 
 | |
|         // Root entity
 | |
|         let mut queue = CommandQueue::default();
 | |
|         let mut commands = Commands::new(&mut queue, &world);
 | |
|         let mut children = Vec::new();
 | |
|         commands
 | |
|             .spawn_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)))
 | |
|             .with_children(|parent| {
 | |
|                 children.push(
 | |
|                     parent
 | |
|                         .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.0)))
 | |
|                         .id(),
 | |
|                 );
 | |
|                 children.push(
 | |
|                     parent
 | |
|                         .spawn_bundle(TransformBundle::from(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() {
 | |
|         let mut world = World::default();
 | |
| 
 | |
|         let mut update_stage = SystemStage::parallel();
 | |
|         update_stage.add_system(transform_propagate_system);
 | |
| 
 | |
|         let mut schedule = Schedule::default();
 | |
|         schedule.add_stage("update", update_stage);
 | |
| 
 | |
|         // 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()
 | |
|                 .insert(Transform::from_xyz(1.0, 0.0, 0.0))
 | |
|                 .id();
 | |
|             commands.entity(parent).with_children(|parent| {
 | |
|                 children.push(
 | |
|                     parent
 | |
|                         .spawn()
 | |
|                         .insert(Transform::from_xyz(0.0, 2.0, 0.0))
 | |
|                         .id(),
 | |
|                 );
 | |
|                 children.push(
 | |
|                     parent
 | |
|                         .spawn()
 | |
|                         .insert(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();
 | |
| 
 | |
|         app.add_system(transform_propagate_system);
 | |
| 
 | |
|         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()
 | |
|             .insert(Transform::from_translation(translation))
 | |
|             .insert(GlobalTransform::default())
 | |
|             .with_children(|builder| {
 | |
|                 child = builder
 | |
|                     .spawn_bundle((Transform::identity(), GlobalTransform::default()))
 | |
|                     .with_children(|builder| {
 | |
|                         grandchild = builder
 | |
|                             .spawn_bundle((Transform::identity(), GlobalTransform::default()))
 | |
|                             .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.query::<&GlobalTransform>();
 | |
|         for global in state.iter(&app.world) {
 | |
|             assert_eq!(
 | |
|                 global,
 | |
|                 &GlobalTransform {
 | |
|                     translation,
 | |
|                     ..Default::default()
 | |
|                 },
 | |
|             );
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     #[should_panic]
 | |
|     fn panic_when_hierarchy_cycle() {
 | |
|         // 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_system(transform_propagate_system);
 | |
| 
 | |
|         fn setup_world(world: &mut World) -> (Entity, Entity) {
 | |
|             let mut grandchild = Entity::from_raw(0);
 | |
|             let child = world
 | |
|                 .spawn()
 | |
|                 .insert_bundle((Transform::identity(), GlobalTransform::default()))
 | |
|                 .with_children(|builder| {
 | |
|                     grandchild = builder
 | |
|                         .spawn()
 | |
|                         .insert_bundle((Transform::identity(), GlobalTransform::default()))
 | |
|                         .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()
 | |
|             .insert_bundle((Transform::default(), GlobalTransform::default()))
 | |
|             .push_children(&[child]);
 | |
|         std::mem::swap(
 | |
|             &mut *app.world.get_mut::<Parent>(child).unwrap(),
 | |
|             &mut *temp.get_mut::<Parent>(grandchild).unwrap(),
 | |
|         );
 | |
| 
 | |
|         app.update();
 | |
|     }
 | |
| }
 |