diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 83fe596766395c..8b665219b4e2b4 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -61,8 +61,9 @@ impl DebugCheckedUnwrap for Option { #[cfg(test)] mod tests { use super::{ReadOnlyWorldQuery, WorldQuery}; - use crate::prelude::{AnyOf, Entity, Or, QueryState, With, Without}; + use crate::prelude::{AnyOf, Changed, Entity, Or, QueryState, With, Without}; use crate::query::{ArchetypeFilter, QueryCombinationIter}; + use crate::schedule::{IntoSystemConfigs, Schedule}; use crate::system::{IntoSystem, Query, System, SystemState}; use crate::{self as bevy_ecs, component::Component, world::World}; use std::any::type_name; @@ -749,4 +750,33 @@ mod tests { let _: [&Foo; 1] = q.many([e]); let _: &Foo = q.single(); } + + // regression test for https://github.com/bevyengine/bevy/pull/8029 + #[test] + fn par_iter_mut_change_detection() { + let mut world = World::new(); + world.spawn((A(1), B(1))); + + fn propagate_system(mut query: Query<(&A, &mut B), Changed>) { + query.par_iter_mut().for_each_mut(|(a, mut b)| { + b.0 = a.0; + }); + } + + fn modify_system(mut query: Query<&mut A>) { + for mut a in &mut query { + a.0 = 2; + } + } + + let mut schedule = Schedule::new(); + schedule.add_systems((propagate_system, modify_system).chain()); + schedule.run(&mut world); + world.clear_trackers(); + schedule.run(&mut world); + world.clear_trackers(); + + let values = world.query::<&B>().iter(&world).collect::>(); + assert_eq!(values, vec![&B(2)]); + } } diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index 72744a8efa7f4d..4d3393c8f0cebf 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -1,4 +1,4 @@ -use crate::world::World; +use crate::{component::Tick, world::World}; use bevy_tasks::ComputeTaskPool; use std::ops::Range; @@ -81,6 +81,8 @@ impl BatchingStrategy { pub struct QueryParIter<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> { pub(crate) world: &'w World, pub(crate) state: &'s QueryState, + pub(crate) last_run: Tick, + pub(crate) this_run: Tick, pub(crate) batching_strategy: BatchingStrategy, } @@ -148,12 +150,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { ) { let thread_count = ComputeTaskPool::get().thread_num(); if thread_count <= 1 { - self.state.for_each_unchecked_manual( - self.world, - func, - self.world.last_change_tick(), - self.world.read_change_tick(), - ); + self.state + .for_each_unchecked_manual(self.world, func, self.last_run, self.this_run); } else { // Need a batch size of at least 1. let batch_size = self.get_batch_size(thread_count).max(1); @@ -161,8 +159,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { self.world, batch_size, func, - self.world.last_change_tick(), - self.world.read_change_tick(), + self.last_run, + self.this_run, ); } } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index d85c2965dec278..65774d40a99cda 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -820,6 +820,8 @@ impl QueryState { QueryParIter { world, state: self, + last_run: world.last_change_tick(), + this_run: world.read_change_tick(), batching_strategy: BatchingStrategy::new(), } } @@ -832,9 +834,12 @@ impl QueryState { #[inline] pub fn par_iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryParIter<'w, 's, Q, F> { self.update_archetypes(world); + let this_run = world.change_tick(); QueryParIter { world, state: self, + last_run: world.last_change_tick(), + this_run, batching_strategy: BatchingStrategy::new(), } } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index e8c54472dae424..bda0401a57fc0b 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -728,6 +728,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { QueryParIter { world: self.world, state: self.state.as_readonly(), + last_run: self.last_run, + this_run: self.this_run, batching_strategy: BatchingStrategy::new(), } } @@ -742,6 +744,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { QueryParIter { world: self.world, state: self.state, + last_run: self.last_run, + this_run: self.this_run, batching_strategy: BatchingStrategy::new(), } }