 b04947d44f
			
		
	
	
		b04947d44f
		
			
		
	
	
	
	
		
			
			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>
		
			
				
	
	
		
			519 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			519 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()
 | |
|         );
 | |
|     }
 | |
| }
 |