Fixed criteria-less systems being re-ran unnecessarily (#1754)

Fixes #1753.

The problem was introduced while reworking the logic around stages' own criteria. Before #1675 they used to be stored and processed inline with the systems' criteria, and systems without criteria used that of their stage. After, criteria-less systems think they should run, always. This PR more or less restores previous behavior; a less cludge solution can wait until after 0.5 - ideally, until stageless.

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Alexander Sepity 2021-03-26 00:31:58 +00:00
parent bf053218bf
commit 500d7469e7
2 changed files with 133 additions and 84 deletions

View File

@ -747,12 +747,6 @@ impl Stage for SystemStage {
self.world_id = Some(world.id()); self.world_id = Some(world.id());
} }
if let ShouldRun::No | ShouldRun::NoAndCheckAgain =
self.stage_run_criteria.should_run(world)
{
return;
}
if self.systems_modified { if self.systems_modified {
self.initialize_systems(world); self.initialize_systems(world);
self.rebuild_orders_and_dependencies(); self.rebuild_orders_and_dependencies();
@ -767,101 +761,127 @@ impl Stage for SystemStage {
self.executor_modified = false; self.executor_modified = false;
} }
// Evaluate run criteria. let mut run_stage_loop = true;
for index in 0..self.run_criteria.len() { while run_stage_loop {
let (run_criteria, tail) = self.run_criteria.split_at_mut(index); let should_run = self.stage_run_criteria.should_run(world);
let mut criteria = &mut tail[0]; match should_run {
match &mut criteria.inner { ShouldRun::No => return,
RunCriteriaInner::Single(system) => criteria.should_run = system.run((), world), ShouldRun::NoAndCheckAgain => continue,
RunCriteriaInner::Piped { ShouldRun::YesAndCheckAgain => (),
input: parent, ShouldRun::Yes => {
system, run_stage_loop = false;
.. }
} => criteria.should_run = system.run(run_criteria[*parent].should_run, world), };
}
}
let mut has_work = true; // Evaluate system run criteria.
while has_work { for index in 0..self.run_criteria.len() {
fn should_run( let (run_criteria, tail) = self.run_criteria.split_at_mut(index);
container: &impl SystemContainer, let mut criteria = &mut tail[0];
run_criteria: &[RunCriteriaContainer], match &mut criteria.inner {
) -> bool { RunCriteriaInner::Single(system) => criteria.should_run = system.run((), world),
matches!( RunCriteriaInner::Piped {
container input: parent,
.run_criteria() system,
.map(|index| run_criteria[index].should_run), ..
None | Some(ShouldRun::Yes) | Some(ShouldRun::YesAndCheckAgain) } => criteria.should_run = system.run(run_criteria[*parent].should_run, world),
)
}
// Run systems that want to be at the start of stage.
for container in &mut self.exclusive_at_start {
if should_run(container, &self.run_criteria) {
container.system_mut().run(world);
} }
} }
// Run parallel systems using the executor. let mut run_system_loop = true;
// TODO: hard dependencies, nested sets, whatever... should be evaluated here. let mut default_should_run = ShouldRun::Yes;
for container in &mut self.parallel { while run_system_loop {
container.should_run = should_run(container, &self.run_criteria); run_system_loop = false;
}
self.executor.run_systems(&mut self.parallel, world);
// Run systems that want to be between parallel systems and their command buffers. fn should_run(
for container in &mut self.exclusive_before_commands { container: &impl SystemContainer,
if should_run(container, &self.run_criteria) { run_criteria: &[RunCriteriaContainer],
container.system_mut().run(world); default: ShouldRun,
) -> bool {
matches!(
container
.run_criteria()
.map(|index| run_criteria[index].should_run)
.unwrap_or(default),
ShouldRun::Yes | ShouldRun::YesAndCheckAgain
)
} }
}
// Apply parallel systems' buffers. // Run systems that want to be at the start of stage.
for container in &mut self.parallel { for container in &mut self.exclusive_at_start {
if container.should_run { if should_run(container, &self.run_criteria, default_should_run) {
container.system_mut().apply_buffers(world); container.system_mut().run(world);
}
} }
}
// Run systems that want to be at the end of stage. // Run parallel systems using the executor.
for container in &mut self.exclusive_at_end { // TODO: hard dependencies, nested sets, whatever... should be evaluated here.
if should_run(container, &self.run_criteria) { for container in &mut self.parallel {
container.system_mut().run(world); container.should_run =
should_run(container, &self.run_criteria, default_should_run);
} }
} self.executor.run_systems(&mut self.parallel, world);
// Check for old component and system change ticks // Run systems that want to be between parallel systems and their command buffers.
self.check_change_ticks(world); for container in &mut self.exclusive_before_commands {
if should_run(container, &self.run_criteria, default_should_run) {
container.system_mut().run(world);
}
}
// Reevaluate run criteria. // Apply parallel systems' buffers.
has_work = false; for container in &mut self.parallel {
let run_criteria = &mut self.run_criteria; if container.should_run {
for index in 0..run_criteria.len() { container.system_mut().apply_buffers(world);
let (run_criteria, tail) = run_criteria.split_at_mut(index); }
let criteria = &mut tail[0]; }
match criteria.should_run {
ShouldRun::No => (), // Run systems that want to be at the end of stage.
ShouldRun::Yes => criteria.should_run = ShouldRun::No, for container in &mut self.exclusive_at_end {
ShouldRun::YesAndCheckAgain | ShouldRun::NoAndCheckAgain => { if should_run(container, &self.run_criteria, default_should_run) {
match &mut criteria.inner { container.system_mut().run(world);
RunCriteriaInner::Single(system) => { }
criteria.should_run = system.run((), world) }
// Check for old component and system change ticks
self.check_change_ticks(world);
// Evaluate run criteria.
let run_criteria = &mut self.run_criteria;
for index in 0..run_criteria.len() {
let (run_criteria, tail) = run_criteria.split_at_mut(index);
let criteria = &mut tail[0];
match criteria.should_run {
ShouldRun::No => (),
ShouldRun::Yes => criteria.should_run = ShouldRun::No,
ShouldRun::YesAndCheckAgain | ShouldRun::NoAndCheckAgain => {
match &mut criteria.inner {
RunCriteriaInner::Single(system) => {
criteria.should_run = system.run((), world)
}
RunCriteriaInner::Piped {
input: parent,
system,
..
} => {
criteria.should_run =
system.run(run_criteria[*parent].should_run, world)
}
} }
RunCriteriaInner::Piped { match criteria.should_run {
input: parent, ShouldRun::Yes => {
system, run_system_loop = true;
.. }
} => { ShouldRun::YesAndCheckAgain | ShouldRun::NoAndCheckAgain => {
criteria.should_run = run_system_loop = true;
system.run(run_criteria[*parent].should_run, world) }
ShouldRun::No => (),
} }
} }
match criteria.should_run {
ShouldRun::Yes | ShouldRun::YesAndCheckAgain => has_work = true,
ShouldRun::No | ShouldRun::NoAndCheckAgain => (),
}
} }
} }
// after the first loop, default to not running systems without run criteria
default_should_run = ShouldRun::No;
} }
} }
} }

View File

@ -652,4 +652,33 @@ mod test {
&MyState::Final &MyState::Final
); );
} }
#[test]
fn issue_1753() {
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
enum AppState {
Main,
}
fn should_run_once(mut flag: ResMut<bool>, test_name: Res<&'static str>) {
assert!(!*flag, "{:?}", *test_name);
*flag = true;
}
let mut world = World::new();
world.insert_resource(State::new(AppState::Main));
world.insert_resource(false);
world.insert_resource("control");
let mut stage = SystemStage::parallel().with_system(should_run_once.system());
stage.run(&mut world);
assert!(*world.get_resource::<bool>().unwrap(), "after control");
world.insert_resource(false);
world.insert_resource("test");
let mut stage = SystemStage::parallel()
.with_system_set(State::<AppState>::get_driver())
.with_system(should_run_once.system());
stage.run(&mut world);
assert!(*world.get_resource::<bool>().unwrap(), "after test");
}
} }