diff --git a/crates/bevy_transform/src/components/mod.rs b/crates/bevy_transform/src/components/mod.rs index 67720a2b4e08e0..b57f77450a9ff7 100644 --- a/crates/bevy_transform/src/components/mod.rs +++ b/crates/bevy_transform/src/components/mod.rs @@ -5,5 +5,5 @@ mod transform; pub use children::Children; pub use global_transform::*; -pub use parent::{Parent, PreviousParent}; +pub use parent::{DirtyParent, Parent, PreviousParent}; pub use transform::*; diff --git a/crates/bevy_transform/src/components/parent.rs b/crates/bevy_transform/src/components/parent.rs index c594d4c4dae2d0..3290c7153f7a43 100644 --- a/crates/bevy_transform/src/components/parent.rs +++ b/crates/bevy_transform/src/components/parent.rs @@ -59,3 +59,6 @@ impl FromWorld for PreviousParent { PreviousParent(Entity::new(u32::MAX)) } } + +#[derive(Component, Debug, Copy, Clone, Eq, PartialEq, Reflect)] +pub struct DirtyParent; diff --git a/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs b/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs index 6fde5d52ebfbbe..753b3f780bdeee 100644 --- a/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs +++ b/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs @@ -2,7 +2,7 @@ use crate::components::*; use bevy_ecs::{ entity::Entity, prelude::Changed, - query::Without, + query::{With, Without}, system::{Commands, Query}, }; use bevy_utils::HashMap; @@ -67,9 +67,23 @@ pub fn parent_update_system( // collect multiple new children that point to the same parent into the same // SmallVec, and to prevent redundant add+remove operations. children_additions.iter().for_each(|(e, v)| { - commands.entity(*e).insert(Children::with(v)); + // Mark the entity with `Dirty` so that systems which run + // in the same stage have a way to `Query` for a missed update + commands + .entity(*e) + .insert_bundle((Children::with(v), DirtyParent)); }); } + +pub fn clean_dirty_parents( + mut commands: Commands, + dirty_parent_query: Query>, +) { + for entity in dirty_parent_query.iter() { + commands.entity(entity).remove::(); + } +} + #[cfg(test)] mod test { use bevy_ecs::{ @@ -77,10 +91,73 @@ mod test { system::CommandQueue, world::World, }; + use bevy_math::vec3; use super::*; use crate::{hierarchy::BuildChildren, transform_propagate_system::transform_propagate_system}; + #[test] + fn correct_transforms_when_no_children() { + let mut world = World::default(); + + let mut update_stage = SystemStage::parallel(); + update_stage.add_system(parent_update_system); + update_stage.add_system(transform_propagate_system); + update_stage.add_system(clean_dirty_parents); + + let mut schedule = Schedule::default(); + schedule.add_stage("update", update_stage); + + let mut command_queue = CommandQueue::default(); + let mut commands = Commands::new(&mut command_queue, &world); + + let translation = vec3(1.0, 0.0, 0.0); + + let parent = commands + .spawn() + .insert(Transform::from_translation(translation)) + .insert(GlobalTransform::default()) + .id(); + + let child = commands + .spawn() + .insert(Transform::default()) + .insert(Transform::default()) + .insert(GlobalTransform::default()) + .insert(Parent(parent)) + .id(); + + let children = vec![child]; + + command_queue.apply(&mut world); + schedule.run(&mut world); + + // check the `Children` structure is spawned + assert_eq!( + world + .get::(parent) + .unwrap() + .0 + .iter() + .cloned() + .collect::>(), + children, + ); + assert!(world.get::(parent).is_some()); + + schedule.run(&mut world); + + assert_eq!( + world.get::(child).unwrap(), + &GlobalTransform { + translation, + ..Default::default() + }, + ); + + assert!(world.get::(parent).is_none()); + } + #[test] fn correct_children() { let mut world = World::default(); diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index f255c613db8148..769ec60bbc9d32 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -9,7 +9,10 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel}; -use prelude::{parent_update_system, Children, GlobalTransform, Parent, PreviousParent, Transform}; +use prelude::{ + clean_dirty_parents, parent_update_system, Children, GlobalTransform, Parent, PreviousParent, + Transform, +}; #[derive(Default)] pub struct TransformPlugin; @@ -47,6 +50,7 @@ impl Plugin for TransformPlugin { transform_propagate_system::transform_propagate_system .label(TransformSystem::TransformPropagate) .after(TransformSystem::ParentUpdate), - ); + ) + .add_system_to_stage(CoreStage::PostUpdate, clean_dirty_parents); } } diff --git a/crates/bevy_transform/src/transform_propagate_system.rs b/crates/bevy_transform/src/transform_propagate_system.rs index a4ab7aadcdf892..d4eb4574df3bb5 100644 --- a/crates/bevy_transform/src/transform_propagate_system.rs +++ b/crates/bevy_transform/src/transform_propagate_system.rs @@ -1,4 +1,4 @@ -use crate::components::{Children, GlobalTransform, Parent, Transform}; +use crate::components::{Children, DirtyParent, GlobalTransform, Parent, Transform}; use bevy_ecs::{ entity::Entity, query::{Changed, With, Without}, @@ -12,13 +12,14 @@ pub fn transform_propagate_system( (Entity, Option<&Children>, &Transform, &mut GlobalTransform), Without, >, + dirty_parent_query: Query>, mut transform_query: Query<(&Transform, &mut GlobalTransform), With>, changed_transform_query: Query>, children_query: Query, (With, With)>, ) { for (entity, children, transform, mut global_transform) in root_query.iter_mut() { let mut changed = false; - if changed_transform_query.get(entity).is_ok() { + if changed_transform_query.get(entity).is_ok() || dirty_parent_query.get(entity).is_ok() { *global_transform = GlobalTransform::from(*transform); changed = true; } @@ -27,6 +28,7 @@ pub fn transform_propagate_system( for child in children.0.iter() { propagate_recursive( &global_transform, + &dirty_parent_query, &changed_transform_query, &mut transform_query, &children_query, @@ -40,6 +42,7 @@ pub fn transform_propagate_system( fn propagate_recursive( parent: &GlobalTransform, + dirty_parent_query: &Query>, changed_transform_query: &Query>, transform_query: &mut Query<(&Transform, &mut GlobalTransform), With>, children_query: &Query, (With, With)>, @@ -47,6 +50,7 @@ fn propagate_recursive( mut changed: bool, ) { changed |= changed_transform_query.get(entity).is_ok(); + changed |= dirty_parent_query.get(entity).is_ok(); let global_matrix = { if let Ok((transform, mut global_transform)) = transform_query.get_mut(entity) { @@ -63,6 +67,7 @@ fn propagate_recursive( for child in children.0.iter() { propagate_recursive( &global_matrix, + &dirty_parent_query, changed_transform_query, transform_query, children_query,