diff --git a/crates/bevy_transform/src/components/mod.rs b/crates/bevy_transform/src/components/mod.rs index 67720a2b4e08e..b57f77450a9ff 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 c594d4c4dae2d..3290c7153f7a4 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 6fde5d52ebfbb..82fd7ac315dd6 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 a `DirtyParent` component 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,72 @@ 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(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 f255c613db814..769ec60bbc9d3 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 a4ab7aadcdf89..860902ec5c415 100644 --- a/crates/bevy_transform/src/transform_propagate_system.rs +++ b/crates/bevy_transform/src/transform_propagate_system.rs @@ -1,6 +1,7 @@ -use crate::components::{Children, GlobalTransform, Parent, Transform}; +use crate::components::{Children, DirtyParent, GlobalTransform, Parent, Transform}; use bevy_ecs::{ entity::Entity, + prelude::Or, query::{Changed, With, Without}, system::Query, }; @@ -13,7 +14,7 @@ pub fn transform_propagate_system( Without, >, mut transform_query: Query<(&Transform, &mut GlobalTransform), With>, - changed_transform_query: Query>, + changed_transform_query: Query, With)>>, children_query: Query, (With, With)>, ) { for (entity, children, transform, mut global_transform) in root_query.iter_mut() { @@ -40,7 +41,7 @@ pub fn transform_propagate_system( fn propagate_recursive( parent: &GlobalTransform, - changed_transform_query: &Query>, + changed_transform_query: &Query, With)>>, transform_query: &mut Query<(&Transform, &mut GlobalTransform), With>, children_query: &Query, (With, With)>, entity: Entity,