 21195a75e6
			
		
	
	
		21195a75e6
		
			
		
	
	
	
	
		
			
			# Objective
Expand `track_change_detection` feature to also track entity spawns and
despawns. Use this to create better error messages.
# Solution
Adds `Entities::entity_get_spawned_or_despawned_by` as well as `{all
entity reference types}::spawned_by`.
This also removes the deprecated `get_many_entities_mut` & co (and
therefore can't land in 0.15) because we don't yet have no Polonius.
## Testing
Added a test that checks that the locations get updated and these
updates are ordered correctly vs hooks & observers.
---
## Showcase
Access location:
```rust
let mut world = World::new();
let entity = world.spawn_empty().id();
println!("spawned by: {}", world.entity(entity).spawned_by());
```
```
spawned by: src/main.rs:5:24
```
Error message (with `track_change_detection`):
```rust
world.despawn(entity);
world.entity(entity);
```
```
thread 'main' panicked at src/main.rs:11:11:
Entity 0v1#4294967296 was despawned by src/main.rs:10:11
```
and without:
```
thread 'main' panicked at src/main.rs:11:11:
Entity 0v1#4294967296 does not exist (enable `track_change_detection` feature for more details)
```
Similar error messages now also exists for `Query::get`,
`World::entity_mut`, `EntityCommands` creation and everything that
causes `B0003`, e.g.
```
error[B0003]: Could not insert a bundle (of type `MaterialMeshBundle<StandardMaterial>`) for entity Entity { index: 7, generation: 1 }, which was despawned by src/main.rs:10:11. See: https://bevyengine.org/learn/errors/#b0003
```
---------
Co-authored-by: kurk070ff <108901106+kurk070ff@users.noreply.github.com>
Co-authored-by: Freya Pines <freya@MacBookAir.lan>
Co-authored-by: Freya Pines <freya@Freyas-MacBook-Air.local>
Co-authored-by: Matty Weatherley <weatherleymatthew@gmail.com>
		
	
			
		
			
				
	
	
		
			147 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! System parameter for computing up-to-date [`GlobalTransform`]s.
 | |
| 
 | |
| use bevy_ecs::{
 | |
|     prelude::Entity,
 | |
|     query::QueryEntityError,
 | |
|     system::{Query, SystemParam},
 | |
| };
 | |
| use bevy_hierarchy::{HierarchyQueryExt, Parent};
 | |
| use thiserror::Error;
 | |
| 
 | |
| use crate::components::{GlobalTransform, Transform};
 | |
| 
 | |
| /// System parameter for computing up-to-date [`GlobalTransform`]s.
 | |
| ///
 | |
| /// Computing an entity's [`GlobalTransform`] can be expensive so it is recommended
 | |
| /// you use the [`GlobalTransform`] component stored on the entity, unless you need
 | |
| /// a [`GlobalTransform`] that reflects the changes made to any [`Transform`]s since
 | |
| /// the last time the transform propagation systems ran.
 | |
| #[derive(SystemParam)]
 | |
| pub struct TransformHelper<'w, 's> {
 | |
|     parent_query: Query<'w, 's, &'static Parent>,
 | |
|     transform_query: Query<'w, 's, &'static Transform>,
 | |
| }
 | |
| 
 | |
| impl<'w, 's> TransformHelper<'w, 's> {
 | |
|     /// Computes the [`GlobalTransform`] of the given entity from the [`Transform`] component on it and its ancestors.
 | |
|     pub fn compute_global_transform(
 | |
|         &self,
 | |
|         entity: Entity,
 | |
|     ) -> Result<GlobalTransform, ComputeGlobalTransformError> {
 | |
|         let transform = self
 | |
|             .transform_query
 | |
|             .get(entity)
 | |
|             .map_err(|err| map_error(err, false))?;
 | |
| 
 | |
|         let mut global_transform = GlobalTransform::from(*transform);
 | |
| 
 | |
|         for entity in self.parent_query.iter_ancestors(entity) {
 | |
|             let transform = self
 | |
|                 .transform_query
 | |
|                 .get(entity)
 | |
|                 .map_err(|err| map_error(err, true))?;
 | |
| 
 | |
|             global_transform = *transform * global_transform;
 | |
|         }
 | |
| 
 | |
|         Ok(global_transform)
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn map_error(err: QueryEntityError, ancestor: bool) -> ComputeGlobalTransformError {
 | |
|     use ComputeGlobalTransformError::*;
 | |
|     match err {
 | |
|         QueryEntityError::QueryDoesNotMatch(entity, _) => MissingTransform(entity),
 | |
|         QueryEntityError::NoSuchEntity(entity, _) => {
 | |
|             if ancestor {
 | |
|                 MalformedHierarchy(entity)
 | |
|             } else {
 | |
|                 NoSuchEntity(entity)
 | |
|             }
 | |
|         }
 | |
|         QueryEntityError::AliasedMutability(_) => unreachable!(),
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Error returned by [`TransformHelper::compute_global_transform`].
 | |
| #[derive(Debug, Error)]
 | |
| pub enum ComputeGlobalTransformError {
 | |
|     /// The entity or one of its ancestors is missing the [`Transform`] component.
 | |
|     #[error("The entity {0:?} or one of its ancestors is missing the `Transform` component")]
 | |
|     MissingTransform(Entity),
 | |
|     /// The entity does not exist.
 | |
|     #[error("The entity {0:?} does not exist")]
 | |
|     NoSuchEntity(Entity),
 | |
|     /// An ancestor is missing.
 | |
|     /// This probably means that your hierarchy has been improperly maintained.
 | |
|     #[error("The ancestor {0:?} is missing")]
 | |
|     MalformedHierarchy(Entity),
 | |
| }
 | |
| 
 | |
| #[cfg(test)]
 | |
| mod tests {
 | |
|     use core::f32::consts::TAU;
 | |
| 
 | |
|     use bevy_app::App;
 | |
|     use bevy_ecs::system::SystemState;
 | |
|     use bevy_hierarchy::BuildChildren;
 | |
|     use bevy_math::{Quat, Vec3};
 | |
| 
 | |
|     use crate::{
 | |
|         components::{GlobalTransform, Transform},
 | |
|         helper::TransformHelper,
 | |
|         plugins::TransformPlugin,
 | |
|     };
 | |
| 
 | |
|     #[test]
 | |
|     fn match_transform_propagation_systems() {
 | |
|         // Single transform
 | |
|         match_transform_propagation_systems_inner(vec![Transform::from_translation(Vec3::X)
 | |
|             .with_rotation(Quat::from_rotation_y(TAU / 4.))
 | |
|             .with_scale(Vec3::splat(2.))]);
 | |
| 
 | |
|         // Transform hierarchy
 | |
|         match_transform_propagation_systems_inner(vec![
 | |
|             Transform::from_translation(Vec3::X)
 | |
|                 .with_rotation(Quat::from_rotation_y(TAU / 4.))
 | |
|                 .with_scale(Vec3::splat(2.)),
 | |
|             Transform::from_translation(Vec3::Y)
 | |
|                 .with_rotation(Quat::from_rotation_z(TAU / 3.))
 | |
|                 .with_scale(Vec3::splat(1.5)),
 | |
|             Transform::from_translation(Vec3::Z)
 | |
|                 .with_rotation(Quat::from_rotation_x(TAU / 2.))
 | |
|                 .with_scale(Vec3::splat(0.3)),
 | |
|         ]);
 | |
|     }
 | |
| 
 | |
|     fn match_transform_propagation_systems_inner(transforms: Vec<Transform>) {
 | |
|         let mut app = App::new();
 | |
|         app.add_plugins(TransformPlugin);
 | |
| 
 | |
|         let mut entity = None;
 | |
| 
 | |
|         for transform in transforms {
 | |
|             let mut e = app.world_mut().spawn(transform);
 | |
| 
 | |
|             if let Some(entity) = entity {
 | |
|                 e.set_parent(entity);
 | |
|             }
 | |
| 
 | |
|             entity = Some(e.id());
 | |
|         }
 | |
| 
 | |
|         let leaf_entity = entity.unwrap();
 | |
| 
 | |
|         app.update();
 | |
| 
 | |
|         let transform = *app.world().get::<GlobalTransform>(leaf_entity).unwrap();
 | |
| 
 | |
|         let mut state = SystemState::<TransformHelper>::new(app.world_mut());
 | |
|         let helper = state.get(app.world());
 | |
| 
 | |
|         let computed_transform = helper.compute_global_transform(leaf_entity).unwrap();
 | |
| 
 | |
|         approx::assert_abs_diff_eq!(transform.affine(), computed_transform.affine());
 | |
|     }
 | |
| }
 |